<?php

class NestedDirectoryException extends Exception { }

class Application_Model_MusicDir {

    /**
     * @holds propel database object
     */
    private $_dir;

    public function __construct($dir)
    {
        $this->_dir = $dir;
    }

    public function getId()
    {
        return $this->_dir->getId();
    }

    public function getType()
    {
        return $this->_dir->getType();
    }

    public function setType($type)
    {
        $this->_dir->setType($type);
    }

    public function getDirectory()
    {
        return $this->_dir->getDirectory();
    }

    public function setDirectory($dir)
    {
        $this->_dir->setDirectory($dir);
        $this->_dir->save();
    }

    public function setExistsFlag($flag){
        $this->_dir->setExists($flag);
        $this->_dir->save();
    }

    public function setWatchedFlag($flag){
        $this->_dir->setWatched($flag);
        $this->_dir->save();
    }

    public function getWatchedFlag(){
        return $this->_dir->getWatched();
    }

    public function getExistsFlag(){
        return $this->_dir->getExists();
    }

    /**
     * There are 2 cases where this function can be called.
     * 1. When watched dir was removed
     * 2. When some dir was watched, but it was unmounted
     *
     *  In case of 1, $userAddedWatchedDir should be true
     *  In case of 2, $userAddedWatchedDir should be false
     *
     *  When $userAddedWatchedDir is true, it will set "Watched" flag to false
     *  otherwise, it will set "Exists" flag to true
     */
    public function remove($userAddedWatchedDir=true)
    {
        $con = Propel::getConnection();

        $music_dir_id = $this->getId();

        $sql = "SELECT DISTINCT s.instance_id from cc_music_dirs as md "
             ." LEFT JOIN cc_files as f on f.directory = md.id"
             ." RIGHT JOIN cc_schedule as s on s.file_id = f.id WHERE md.id = $music_dir_id";

        $show_instances = $con->query($sql)->fetchAll();

        // get all the files on this dir
        $sql = "UPDATE cc_files SET file_exists = 'f' WHERE id IN (SELECT f.id FROM cc_music_dirs as md "
            ." LEFT JOIN cc_files as f on f.directory = md.id WHERE md.id = $music_dir_id)";
        $affected = $con->exec($sql);

        // set RemovedFlag to true
        if ($userAddedWatchedDir) {
            self::setWatchedFlag(false);
        } else {
            self::setExistsFlag(false);
        }
        //$res = $this->_dir->delete();

        foreach ($show_instances as $show_instance_row) {
            $temp_show = new Application_Model_ShowInstance($show_instance_row["instance_id"]);
            $temp_show->updateScheduledTime();
        }
        Application_Model_RabbitMq::PushSchedule();
    }

    /**
     * Checks if p_dir1 is the ancestor of p_dir2. Returns
     * true if it is the ancestor, false otherwise. Note that
     * /home/user is considered the ancestor of /home/user
     *
     * @param string $p_dir1
     *      The potential ancestor directory.
     * @param string $p_dir2
     *      The potential descendent directory.
     * @return boolean
     *      Returns true if it is the ancestor, false otherwise.
     */
    private static function isAncestorDir($p_dir1, $p_dir2){
        if (strlen($p_dir1) > strlen($p_dir2)){
            return false;
        }

        return substr($p_dir2, 0, strlen($p_dir1)) == $p_dir1;
    }

    /**
     * Checks whether the path provided is a valid path. A valid path
     * is defined as not being nested within an existing watched directory,
     * or vice-versa. Throws a NestedDirectoryException if invalid.
     *
     * @param string $p_path
     *      The path we want to validate
     * @return void
     */
    public static function isPathValid($p_path){
        $dirs = self::getWatchedDirs();
        $dirs[] = self::getStorDir();

        foreach ($dirs as $dirObj){
            $dir = $dirObj->getDirectory();
            $diff = strlen($dir) - strlen($p_path);
            if ($diff == 0){
                if ($dir == $p_path){
                    throw new NestedDirectoryException("'$p_path' is already watched.");
                }
            } else if ($diff > 0){
                if (self::isAncestorDir($p_path, $dir)){
                    throw new NestedDirectoryException("'$p_path' contains nested watched directory: '$dir'");
                }
            } else { /* diff < 0*/
                if (self::isAncestorDir($dir, $p_path)){
                    throw new NestedDirectoryException("'$p_path' is nested within existing watched directory: '$dir'");
                }
            }
        }
    }

    /** There are 2 cases where this function can be called.
     * 1. When watched dir was added
     * 2. When some dir was watched, but it was unmounted somehow, but gets mounted again
     *
     *  In case of 1, $userAddedWatchedDir should be true
     *  In case of 2, $userAddedWatchedDir should be false
     *
     *  When $userAddedWatchedDir is true, it will set "Removed" flag to false
     *  otherwise, it will set "Exists" flag to true
     *
     *  @param $nestedWatch - if true, bypass path check, and Watched to false
    **/
    public static function addDir($p_path, $p_type, $userAddedWatchedDir=true, $nestedWatch=false)
    {
        if(!is_dir($p_path)){
            return array("code"=>2, "error"=>"'$p_path' is not a valid directory.");
        }
        $real_path = Application_Common_OsPath::normpath($p_path)."/";
        if($real_path != "/"){
            $p_path = $real_path;
        }

        $exist_dir = self::getDirByPath($p_path);

        if( $exist_dir == NULL ){
            $temp_dir = new CcMusicDirs();
            $dir = new Application_Model_MusicDir($temp_dir);
        }else{
            $dir = $exist_dir;
        }

        $dir->setType($p_type);
        $p_path = Application_Common_OsPath::normpath($p_path)."/";


        try {
            /* isPathValid() checks if path is a substring or a superstring of an
             * existing dir and if not, throws NestedDirectoryException */
            if(!$nestedWatch){
                self::isPathValid($p_path);
            }
            if($userAddedWatchedDir){
                $dir->setWatchedFlag(true);
            }else{
                if($nestedWatch){
                    $dir->setWatchedFlag(false);
                }
                $dir->setExistsFlag(true);
            }
            $dir->setDirectory($p_path);

            return array("code"=>0);
        } catch (NestedDirectoryException $nde){
            $msg = $nde->getMessage();
            return array("code"=>1, "error"=>"$msg");
        } catch(Exception $e){
            return array("code"=>1, "error"=>"'$p_path' is already set as the current storage dir or in the watched folders list");
        }

    }

    /** There are 2 cases where this function can be called.
     * 1. When watched dir was added
     * 2. When some dir was watched, but it was unmounted somehow, but gets mounted again
     *
     *  In case of 1, $userAddedWatchedDir should be true
     *  In case of 2, $userAddedWatchedDir should be false
     *
     *  When $userAddedWatchedDir is true, it will set "Watched" flag to true
     *  otherwise, it will set "Exists" flag to true
    **/
    public static function addWatchedDir($p_path, $userAddedWatchedDir=true, $nestedWatch=false)
    {
        $res = self::addDir($p_path, "watched", $userAddedWatchedDir, $nestedWatch);

        if ($res['code'] == 0){

            //convert "linked" files (Airtime <= 1.8.2) to watched files.
            $propel_link_dir = CcMusicDirsQuery::create()
               ->filterByType('link')
               ->findOne();

            //see if any linked files exist.
            if (isset($propel_link_dir)) {

                //newly added watched directory object
                $propel_new_watch = CcMusicDirsQuery::create()
                   ->filterByDirectory(Application_Common_OsPath::normpath($p_path)."/")
                   ->findOne();

                //any files of the deprecated "link" type.
                $link_files = CcFilesQuery::create()
                   ->setFormatter(ModelCriteria::FORMAT_ON_DEMAND)
                   ->filterByDbDirectory($propel_link_dir->getId())
                   ->find();

                $newly_watched_dir = $propel_new_watch->getDirectory();

                foreach ($link_files as $link_file) {
                    $link_filepath = $link_file->getDbFilepath();

                    //convert "link" file into a watched file.
                    if ((strlen($newly_watched_dir) < strlen($link_filepath)) && (substr($link_filepath, 0, strlen($newly_watched_dir)) === $newly_watched_dir)) {

                        //get the filepath path not including the watched directory.
                        $sub_link_filepath = substr($link_filepath, strlen($newly_watched_dir));

                        $link_file->setDbDirectory($propel_new_watch->getId());
                        $link_file->setDbFilepath($sub_link_filepath);
                        $link_file->save();
                    }
                }
            }

            $data = array();
            $data["directory"] = $p_path;
            Application_Model_RabbitMq::SendMessageToMediaMonitor("new_watch", $data);
        }
        return $res;
    }

    public static function getDirByPK($pk)
    {
        $dir = CcMusicDirsQuery::create()->findPK($pk);

        $mus_dir = new Application_Model_MusicDir($dir);

        return $mus_dir;
    }

    public static function getDirByPath($p_path)
    {
        $dir = CcMusicDirsQuery::create()
                    ->filterByDirectory($p_path)
                    ->findOne();
        if($dir == NULL){
            return null;
        }
        else{
            $mus_dir = new Application_Model_MusicDir($dir);
            return $mus_dir;
        }
    }

    /**
     * Search and returns watched dirs
     *
     * @param $exists search condition with exists flag
     * @param $watched search condition with watched flag
     */
    public static function getWatchedDirs($exists=true, $watched=true)
    {
        $result = array();

        $dirs = CcMusicDirsQuery::create()
                    ->filterByType("watched");
        if($exists !== null){
            $dirs = $dirs->filterByExists($exists);
        }
        if($watched !== null){
            $dirs = $dirs->filterByWatched($watched);
        }
         $dirs = $dirs->find();

        foreach($dirs as $dir) {
            $result[] = new Application_Model_MusicDir($dir);
        }

        return $result;
    }

    public static function getStorDir()
    {
        $dir = CcMusicDirsQuery::create()
                    ->filterByType("stor")
                    ->findOne();

        $mus_dir = new Application_Model_MusicDir($dir);

        return $mus_dir;
    }

    public static function setStorDir($p_dir)
    {
        // we want to be consistent when storing dir path.
        // path should always ends with trailing '/'
        $p_dir = Application_Common_OsPath::normpath($p_dir)."/";
        if(!is_dir($p_dir)){
            return array("code"=>2, "error"=>"'$p_dir' is not a valid directory.");
        }else if(Application_Model_Preference::GetImportTimestamp()+10 > time()){
            return array("code"=>3, "error"=>"Airtime is currently importing files. Please wait until this is complete before changing the storage directory.");
        }
        $dir = self::getStorDir();
        // if $p_dir doesn't exist in DB
        $exist = $dir->getDirByPath($p_dir);
        if($exist == NULL){
            $dir->setDirectory($p_dir);
            $dirId = $dir->getId();
            $data = array();
            $data["directory"] = $p_dir;
            $data["dir_id"] = $dirId;
            Application_Model_RabbitMq::SendMessageToMediaMonitor("change_stor", $data);
            return array("code"=>0);
        }else{
            return array("code"=>1, "error"=>"'$p_dir' is already set as the current storage dir or in the watched folders list.");
        }
    }

    public static function getWatchedDirFromFilepath($p_filepath)
    {
        $dirs = CcMusicDirsQuery::create()
                    ->filterByType(array("watched", "stor"))
                    ->filterByExists(true)
                    ->filterByWatched(true)
                    ->find();

        foreach($dirs as $dir) {
            $directory = $dir->getDirectory();
            if (substr($p_filepath, 0, strlen($directory)) === $directory) {
                $mus_dir = new Application_Model_MusicDir($dir);
                return $mus_dir;
            }
        }

        return null;
    }

    /** There are 2 cases where this function can be called.
     * 1. When watched dir was removed
     * 2. When some dir was watched, but it was unmounted
     *
     *  In case of 1, $userAddedWatchedDir should be true
     *  In case of 2, $userAddedWatchedDir should be false
     *
     *  When $userAddedWatchedDir is true, it will set "Watched" flag to false
     *  otherwise, it will set "Exists" flag to true
    **/
    public static function removeWatchedDir($p_dir, $userAddedWatchedDir=true){

        //make sure that $p_dir has a trailing "/"
        $real_path = Application_Common_OsPath::normpath($p_dir)."/";
        if($real_path != "/"){
            $p_dir = $real_path;
        }
        $dir = Application_Model_MusicDir::getDirByPath($p_dir);
        if (is_null($dir)) {
            return array("code"=>1, "error"=>"'$p_dir' doesn't exist in the watched list.");
        } else {
            $dir->remove($userAddedWatchedDir);
            $data = array();
            $data["directory"] = $p_dir;
            Application_Model_RabbitMq::SendMessageToMediaMonitor("remove_watch", $data);
            return array("code"=>0);
        }
    }

    public static function splitFilePath($p_filepath)
    {
        $mus_dir = self::getWatchedDirFromFilepath($p_filepath);

        if(is_null($mus_dir)) {
            return null;
        }

        $length_dir = strlen($mus_dir->getDirectory());
        $fp = substr($p_filepath, $length_dir);

        return array($mus_dir->getDirectory(), trim($fp));
    }

}