<?php defined('SYSPATH') || die('No direct script access.');

/**
 * @property-read $ID          int
 * @property      $AuthorID    int
 * @property      $GradeID     int
 * @property      $GradeNum    int
 * @property      Model_Faculty $Faculty
 * @property      $FacultyID   int
 * @property      $FacultyName string
 * @property      $Degree      string  bachelor / master
 * @property      $SubjectID   int
 * @property      $SubjectName string
 * @property      $SubjectAbbr string
 * @property      $SemesterID  int
 * @property      $Lectures    int
 * @property      $Practice    int
 * @property      $Labs        int
 * @property      $Type        string
 * @property      $Subtype     string
 * @property      $IsLocked    bool
 * @property      $IsBonus     bool
 * @property      $IsMapCreated bool
 * @property      $Milestone   int
 * @property      $CompoundDiscID  int
 * @property      $CompoundDiscName string
 */
class Model_Discipline extends Model_Container
{
    const EXAM = 'exam';
    const CREDIT = 'credit';
    const GRADING_CREDIT = 'grading_credit';


    public function __get($name) {
        if ($name === 'Faculty' && !isset($this->data[$name]))
            $this->data[$name] = Model_Faculty::with(parent::__get('FacultyID'));
        return parent::__get($name);
    }

    /**
     * @param $id int discipline id
     * @return array data from <tt>view_disciplines</tt> table
     * @throws HTTP_Exception if discipline does not exist
     */
    protected function getRawData($id) {
        $sql = 'SELECT * FROM Discipline_GetInfo(:id)';
        $info = DB::query(Database::SELECT, $sql)
            ->param(':id', $id)->execute();

        if ($info->count() == 0)
            throw new InvalidArgumentException(Error::DISCIPLINE_NOT_FOUND);
        return $info->offsetGet(0);
    }

    /**
     * @param $id int submodule id
     * @return array data from <tt>view_disciplines</tt> table
     * @throws HTTP_Exception if discipline does not exist
     */
    protected function getRawDataBySubmoduleID($id) {
        $sql = 'SELECT * FROM Discipline_GetInfoBySubmodule(:id)';
        $info = DB::query(Database::SELECT, $sql)
            ->param(':id', $id)->execute();

        if ($info->count() == 0)
            throw new InvalidArgumentException(Error::DISCIPLINE_NOT_FOUND);
        return $info->offsetGet(0);
    }

    /**
     * Creation of new discipline from raw data.
     * @return Model_Helper_DisciplineBuilder
     * todo: may be we need a factory pattern
     */
    static public function make() {
        return new Model_Helper_DisciplineBuilder();
    }

    /**
     * Create new discipline in db, based on $data.
     */
    protected function create() {
        $sql = 'SELECT * FROM Discipline_Create(AuthorID, GradeID, SubjectID, Type, Lectures, Practice, 
                Labs, FacultyID, SemesterID, Subtype) AS "ID"';

        $this->data[self::$ID_FIELD] = DB::query(Database::SELECT, $sql)
            ->parameters($this->data)
            ->execute()[0]['ID'];
    }

    public function delete() {
        $sql = 'SELECT * FROM Discipline_Delete(:id)';
        DB::query(Database::SELECT, $sql)->param(':id', $this->ID)->execute();
    }

    public function getInternalInfo() {
        $sql = 'SELECT * FROM discipline_get_internals(:id)';
        return DB::query(Database::SELECT, $sql)
            ->param(':id', $this->ID)
            ->execute()->as_array();
    }

    // todo: should return Model_Group[]
    public function getGroups() {
        $sql = 'SELECT * FROM GetGroupsForDiscipline(:id)';
        return DB::query(Database::SELECT, $sql)
            ->param(':id', $this->ID)
            ->execute()->as_array();
    }

    /** Get groups with separately attached students. */
    public function getAllGroups() {
        // http://gitlab.mmcs.sfedu.ru/it-lab/grade/issues/50
        // для получения списка групп для всех дисциплин на странице преподавателя
        // нужно сделать несколько запросов к GetGroupsForDisciplineAll
        // за одно подключение к базе данных
        // но тогда функция возвращает один и тот же ответ
        // поэтому каждый раз подключаемся заново
        $db = Kohana_Database::instance();
        $db->disconnect();
        $sql = 'SELECT * FROM GetGroupsForDisciplineAll(:id)';
        return DB::query(Database::SELECT, $sql)->param(':id', $this->ID)->execute();
    }

    public function hasSubgroups() {
        $sql = 'SELECT * FROM Discipline_HasSubgroups(:id) AS "res"';
        return DB::query(Database::SELECT, $sql)
            ->param(':id', $this->ID)
            ->execute()->get('res');
    }
    
    public function getAllSubgroups() {
        $sql = 'SELECT * FROM Discipline_GetSubgroups(:DisciplineID)';

        return DB::query(Database::SELECT, $sql)
            ->parameters([
                ':DisciplineID'    => $this->ID,
            ])->execute();
    }

    /** @return Model_Student[] */
    public function getStudents() {
        return Model_Students::ofDiscipline($this);
    }

    // todo: should return Model_Teacher[]
    public function getTeachers() {
        $sql = 'SELECT * FROM GetTeachersForDiscipline(:id)';
        return DB::query(Database::SELECT, $sql)
            ->param(':id', $this->ID)->execute();
    }

    public function hasTeacher($teacherID) {
        $sql = 'SELECT * FROM InternalIsTeacherBound(:teacher, :discipline) AS "res"';
        return DB::query(Database::SELECT, $sql)
            ->param(':discipline', $this->ID)
            ->param(':teacher', $teacherID)
            ->execute()->get('res');
    }

    public function bindGlobal($name, $semester, $globalid) {
        $sql = 'SELECT * FROM Discipline_BindGlobal(:id, :semester, :name, :globalid)';
        DB::query(Database::SELECT, $sql)
            ->param(':id', $this->ID)
            ->param(':semester', $semester)
            ->param(':name', $name)
            ->param(':globalid', $globalid)
            ->execute();
    }

    public function unbindAll() {
        $sql = 'SELECT * FROM InternalUnbindAll(:id)';
        DB::query(Database::SELECT, $sql)
            ->param(':id', $this->ID)
            ->execute();
    }

    public function bind(Model_Teacher $teacher) {
        if ($this->ID == $teacher->ID)
            return;

        $sql = 'SELECT * FROM Discipline_BindTeacher(:id, :teacher)';
        DB::query(Database::SELECT, $sql)
            ->param(':teacher', $teacher->ID)
            ->param(':id', $this->ID)
            ->execute();
    }

    public function unbind(Model_Teacher $teacher) {
        if ($this->ID == $teacher->ID)
            return;

        $sql = 'SELECT * FROM Discipline_UnbindTeacher(:id, :teacher)';
        DB::query(Database::SELECT, $sql)
            ->param(':teacher', $teacher->ID)
            ->param(':id', $this->ID)
            ->execute();
    }

    /**
     * Bind teacher and delegate him the discipline.
     * @param Model_Teacher $teacher
     */
    public function delegateTo(Model_Teacher $teacher) {
        if ($this->ID == $teacher->ID)
            return;

        $sql = 'SELECT * FROM Discipline_Delegate(:id, :teacher)';
        DB::query(Database::SELECT, $sql)
            ->param(':teacher', $teacher->ID)
            ->param(':id', $this->ID)
            ->execute();
    }

    public function changeGradeUnsafe($grade) {
        if ($this->GradeID == $grade)
            return 0;

        $sql = 'SELECT * FROM Discipline_SetGradeUnsafe(:discipline, :grade) AS "ErrorCode"';
        return DB::query(Database::SELECT, $sql)
            ->parameters([
                ':discipline' => $this->ID,
                ':grade'      => $grade,
            ])->execute()->get('"ErrorCode"');
    }

    public function changeGrade($teacherID, $grade) {
        if (($this->AuthorID == $teacherID) && $this->IsLocked !== true){
            return $this->changeGradeUnsafe($grade);
        }
        return -1;
    }


    /**
     * Time machine for discipline.
     * @param $stage int number from 0 to 3
     */
    public function setMilestone($stage) {
        if ($stage < 0 || $stage > 3)
            throw new LogicException('Milestone argument is incorrect!');

        $this->Milestone = $stage;

        $sql = 'SELECT * FROM RestrictAfterMilestone(:discipline, :milestone)';
        DB::query(Database::SELECT, $sql)
            ->parameters([
                ':discipline' => $this->ID,
                ':milestone'  => $stage,
            ])->execute();
    }

    // ищет межфакультетские дисциплины по семестру, ведущему факультету, предмету и типу
    public static function find_global($semesterID, $globalExternalID) {
        $sql = 'SELECT * FROM Discipline_Find_Global(:semesterID, :globalExternalID) AS "ID"';
        $data = DB::query(Database::SELECT, $sql)
            ->parameters([
                ':semesterID'  => $semesterID,
                ':globalExternalID'        => $globalExternalID
            ])->execute();
        $cnt = count($data);
        if ($cnt > 1) {
            throw new Database_Exception('There are '.$cnt.' global disciplines found, but only one was expected!');
        }
        elseif ($cnt == 1) {
            return Model_Discipline::load($data[0]['ID']);
        }

        return null;
    }

    public static function update_global($globalExternalID, $globalName) {
        $sql = 'SELECT * FROM Discipline_Update_Global(:globalExternalID, :globalName) AS "Num"';
        return DB::query(Database::SELECT, $sql)
            ->parameters([
                ':globalExternalID'        => $globalExternalID,
                ':globalName' => $globalName,
            ])->execute()->get('"Num"');
    }

    public static function find($studyPlanID, $semesterID, $subjectID, $type) {
        $sql = 'SELECT * FROM Discipline_Find(:studyPlanID, :semesterID, :subjectID, :type) AS "ID"';
            $data = DB::query(Database::SELECT, $sql)
                ->parameters([
                    ':studyPlanID' => $studyPlanID,
                    ':semesterID'  => $semesterID,
                    ':subjectID'   => $subjectID,
                    ':type'        => $type,
                ])->execute();
            $cnt = count($data);
            if ($cnt > 1) {
                throw new Database_Exception('There are '.$cnt.' disciplines found, but only one was expected!');
        }
        elseif ($cnt == 1) {
            return Model_Discipline::load($data[0]['ID']);
        }

        return null;
    }

    // TODO: доделать этот метод http://gitlab.mmcs.sfedu.ru/it-lab/grade/issues/362
    public function update() {
        throw new BadMethodCallException('Method is not implemented yet!');
// тут нужно делать апдейт записи в БД по полям уже указанным в модели
//        $sql = 'SELECT * FROM Discipline_ChangeInfo(ID, AuthorID, GradeID, SubjectID, Type, Lectures, Practice,
//                Labs, FacultyID, SemesterID, Subtype) AS "Success"';
//        $res = DB::query(Database::SELECT, $sql)
//            ->parameters($this->getRawData($this->ID))
//            ->execute();
//        return $res['Success'];
    }

    public function clearModules($teacherID) {
        $map = Model_Map::of($this);

        $modules = $map->getModules('all');
        try {
            foreach ($modules as $record) {
                if ($record['Type'] != 'extra' && $record['Type'] != 'exam') {
                    $deleted = Model_Map::deleteModule($teacherID, $record['ID']);
                    if ($deleted) {
                        return 1;
                    }
                }
            }
        } catch (Exception $e) {
            return 0;
        }
        return 1;
    }

    public function hasModules() {
        $sql = 'SELECT * FROM Discipline_hasMap(:discipline) as "Num"';
        $res =  DB::query(Database::SELECT, $sql)
            ->parameters([
                ':discipline' => $this->ID
            ])->execute()->get("Num");
        return $res;
    }

    public function isGlobal() {
        $sql = 'SELECT * FROM Discipline_isGlobal(:discipline) as "Num"';
        $res =  DB::query(Database::SELECT, $sql)
            ->parameters([
                ':discipline' => $this->ID
            ])->execute()->get("Num");
        return $res;
    }

    public function setInactive() {
        $sql = 'SELECT * FROM Discipline_setInactive(:discipline) as "Num"';
        $res =  DB::query(Database::SELECT, $sql)
            ->parameters([
                ':discipline' => $this->ID
            ])->execute()->get("Num");
        return $res;
    }

    public function setGlobal() {
        $sql = 'SELECT * FROM Discipline_setGlobal(:discipline) as "Num"';
        $res =  DB::query(Database::SELECT, $sql)
            ->parameters([
                ':discipline' => $this->ID
            ])->execute()->get("Num");
        return $res;
    }

    public function setNotGlobal() {
        $sql = 'SELECT * FROM Discipline_setNotGlobal(:discipline) as "Num"';
        $res =  DB::query(Database::SELECT, $sql)
            ->parameters([
                ':discipline' => $this->ID
            ])->execute()->get("Num");
        return $res;
    }

    public function setGlobalStub() {
        $sql = 'SELECT * FROM Discipline_setStub(:discipline) as "Num"';
        $res =  DB::query(Database::SELECT, $sql)
            ->parameters([
                ':discipline' => $this->ID
            ])->execute()->get("Num");
        return $res;
    }

    public function setNotStub() {
        $sql = 'SELECT * FROM Discipline_setNotStub(:discipline) as "Num"';
        $res =  DB::query(Database::SELECT, $sql)
            ->parameters([
                ':discipline' => $this->ID
            ])->execute()->get("Num");
        return $res;
    }

    public function setActive() {
        $sql = 'SELECT * FROM Discipline_setActive(:discipline) as "Num"';
        $res =  DB::query(Database::SELECT, $sql)
            ->parameters([
                ':discipline' => $this->ID
            ])->execute()->get("Num");
        return $res;
    }

    public function copyDisciplineMapFrom($disciplineToID, $disciplineFromID)
    {
        $sql = 'SELECT * FROM copy_map(:from, :to) as "Num"';
        return DB::query(Database::SELECT, $sql)
            ->parameters([
                ':from' => $disciplineFromID,
                ':to'  => $disciplineToID,
            ])->execute()->get("Num");
    }

    public function copyDisciplineStructureFrom($teacherID, $disciplineFromID) {
        $mapFrom = Model_Map::getRoadmap($disciplineFromID, 'all');
        $mapTo = Model_Map::of($this);

        $prevModuleID = 0;
        $moduleID = null;
        foreach ($mapFrom as $record) {
            // проверка повтотрения модуля
            if ($prevModuleID != (int)$record['ModuleID']) {
                $prevModuleID = (int)$record['ModuleID'];
                // копируем текущий модуль
                if ($record['ModuleType'] == 'regular') {
                    $moduleID = $mapTo->addModule($teacherID, $record['ModuleName']);
                } elseif ($record['ModuleType'] == 'bonus') {
                    $moduleID = $mapTo->addModuleBonus($teacherID);
                } else {
                    break;
                }
            }

            // копируем текуще мероприятие (Submodule)
            Model_Map::addSubmodule($teacherID, $moduleID, $record['SubmoduleType'],
                $record['MaxRate'], $record['SubmoduleName']);
        }
    }

    public function isTaskOpen($recordbook, $modulenumber) {
        $sql = 'SELECT * FROM CheckTaskOpen(:discipline, :modulenumber, :recordbookid) AS "res"';
        return DB::query(Database::SELECT, $sql)
            ->param(':discipline', $this->ID)
            ->param(':modulenumber', $modulenumber)
            ->param(':recordbookid', $recordbook)
            ->execute()->get('res');
    }

}