<?php

/**
 * Copyright 2005-2017 de Solis Soluções Livres Ltda.
 *
 * Este arquivo é parte do programa Gnuteca.
 *
 * O Gnuteca é um software de propriedade da SOLIS, sendo desenvolvido
 * e mantido exclusivamente por esta empresa.
 *
 * A licença de uso está disponível mediante aquisição exclusiva junto à
 * SOLIS. A licença é concedida sem caráter de exclusividade ao licenciado.
 * Os direitos de uso são perpétuos.
 *
 * Embora os códigos fontes sejam fornecidos, o software é de propriedade
 * da SOLIS, não sendo permitido ao adquirente da licença a sua revenda,
 * empréstimo ou cessão (onerosa ou não) à terceiros. Também não é permitido,
 * a qualquer título e tempo, promover no software qualquer tipo de alienação,
 * reprodução, distribuição, divulgação, registro, licenciamento, transferência
 * ou qualquer outro ato que prejudique ou comprometa os direitos de propriedade
 * de software, o nome e a imagem da sua proprietária e do próprio software,
 * além de configurar concorrência à SOLIS.
 *
 * O licenciado, com o acesso ao código fonte do software, terá o direito de
 * promover mudanças no respectivo código. No entanto, nas situações em que ele
 * contar com o suporte oficial prestado pela SOLIS, não poderá promover mudanças
 * no código fonte, sob pena de perda do referido suporte.
 *
 * Para conhecer em detalhes o Termo de Licenciamento do Software Gnuteca
 * leia o arquivo “LICENCA.txt” disponível junto ao código deste software.
 * 
 * 
 *
 *
 *
 *
 *
 * */
class BusinessGnuteca3BusHoliday extends GBusiness
{
    public $busLibraryUnitIsClosed;
    public $colsNoId;
    public $holidayId;
    public $date;
    public $endDate;
    public $description;
    public $occursAllYear;
    public $libraryUnitId;
    public $holidayIdS;
    public $dateS;
    public $beginDateS;
    public $endDateS;
    public $descriptionS;
    public $occursAllYearS;
    public $libraryUnitIdS;
    public $days;

    public function __construct()
    {
        parent::__construct();
        $this->tables = 'gtcHoliday';
        $this->colsNoId = 'date,
                           endDate,
                           description,
                           occursAllYear,
                           libraryUnitId';
        $this->id = 'holidayId';
        $this->columns = 'holidayId, ' . $this->colsNoId;
        $this->busLibraryUnitIsClosed = $this->MIOLO->getBusiness($this->module, 'BusLibraryUnitIsClosed');
    }

    /**
     * List all records from the table handled by the class
     *
     * @param: None
     *
     * @returns (array): Return an array with the entire table
     *
     * */
    public function listHoliday($object = FALSE)
    {
        $this->clear();
        $this->setColumns($this->columns);
        $this->setTables($this->tables);
        $sql = $this->select();
        $rs = $this->query($sql, $object);
        return $rs;
    }

    /**
     * Return a specific record from the database
     *
     * @param $moduleConfig (integer): Primary key of the record to be retrieved
     * @param $parameter (integer): Primary key of the record to be retrieved
     *
     * @return (object): Return an object of the type handled by the class
     *
     * */
    public function getHoliday($holidayId)
    {
        $data = array( $holidayId );

        $this->clear();
        $this->setColumns($this->columns);
        $this->setTables($this->tables);
        $this->setWhere('holidayId = ?');
        $sql = $this->select($data);
        $rs = $this->query($sql, $toObject = true);
        $this->setData($rs[0]);

        return $this;
    }

    /**
     * Do a search on the database table handled by the class
     *
     * @param $filters (object): Search filters
     *
     * @return (array): An array containing the search results
     * */
    public function searchHoliday()
    {
        $this->clear();

        if ( $v = $this->holidayIdS )
        {
            $this->setWhere('holidayId = ?');
            $data[] = $v;
        }
        if ( $v = $this->dateS )
        {
            $this->setWhere('date(date) >= ?');
            $data[] = $v;
        }
        if ( $v = $this->beginDateS )
        {
            $this->setWhere('date(date) >= ?');
            $data[] = $v;
        }
        if ( $v = $this->endDateS )
        {
            $this->setWhere('date(date) <= ?');
            $data[] = $v;
        }
        if ( $v = $this->descriptionS )
        {
            $this->setWhere('lower(description) LIKE lower(?)');
            $data[] = $v . '%';
        }
        if ( $v = $this->occursAllYearS )
        {
            $this->setWhere('occursallyear = ?');
            $data[] = $v;
        }
        if ( $this->libraryUnitIdS )
        {
            $this->setWhere('A.libraryUnitId in (' . $this->libraryUnitIdS . ')');
        }

        $this->setTables('gtcHoliday       A
                LEFT JOIN gtcLibraryUnit   B
                       ON (A.libraryUnitId = B.libraryUnitId)');
        $this->setColumns('A.holidayId,
                           A.date,
                           A.enddate,
                           A.description,
                           A.occursallyear,
                           A.libraryUnitId AS libraryUnitId,
                           (CASE WHEN B.libraryName IS NOT NULL THEN B.libraryName ELSE \'Todas bibliotecas\' END) AS libraryName');
        $this->setOrderBy('date DESC');
        $sql = $this->select($data);
        $rs = $this->query($sql);

        return $rs;
    }

    /**
     * Insert a new record
     *
     * @param $data (object): An object of the type handled by the class
     *
     * @return True if succed, otherwise False
     *
     * */
    public function insertHoliday()
    {
        $data = $this->associateData($this->colsNoId);

        $this->clear();
        $this->setColumns($this->colsNoId);
        $this->setTables($this->tables);
        $sql = $this->insert($data);
        $rs = $this->execute($sql);

        return $rs;
    }

    /**
     * Update data from a specific record
     *
     * @param $data (object): Data which will replace the old record data
     *
     * @return (boolean): True if succeed, otherwise False
     *
     * */
    public function updateHoliday()
    {
        $data = $this->associateData($this->colsNoId . ', holidayId');

        $this->clear();
        $this->setColumns($this->colsNoId);
        $this->setTables($this->tables);
        $this->setWhere('holidayId = ?');
        $sql = $this->update($data);
        $rs = $this->execute($sql);

        return $rs;
    }

    /**
     * Delete a record
     *
     * @param $moduleConfig (string): Primary key for deletion
     * @param $parameter (string): Primary key for deletion
     *
     * @return (boolean): True if succeed, otherwise False
     *
     * */
    public function deleteHoliday($holidayId)
    {
        $data = array( $holidayId );

        $this->clear();
        $this->setTables($this->tables);
        $this->setWhere('holidayId = ?');
        $sql = $this->delete($data);
        $rs = $this->execute($sql);

        return $rs;
    }

	/**
     * Função calcula dias de feriado entre duas datas.
     * Desconsidera na contagem os dias em que a biblioteca estiver fechada.
     *
     * @return dias de feriados entre períodos
	 *
     * @param $beginData data inicial
     * @param $endDate data final do período de verificação
     * @para $libraryUnitId código da unidade de biblioteca 
     *
     * */
    public function amountDays($beginDate, $endDate, $libraryUnitId)
    {
        // Alteração realizada devido a #86949, onde foi constatado que não era levado em consideração feriados de todos os anos que são de um ano diferente do atual.
        $amountDays = $this->calculateAmoundDays($beginDate, $endDate, $libraryUnitId);

        $beginYear = GDate::construct($beginDate)->getYear();
        $endYear = GDate::construct($endDate)->getYear();


        for ( $i = $beginYear; $i <= $endYear; $i++ )
        {
            $amountDays += $a = $this->calculateAmoundDays($beginDate, $endDate, $libraryUnitId, $i);
        }


        $days = $this->getHolidaysInTheFine($beginDate, $endDate, $libraryUnitId);
        $amountDays = $days;

        return $amountDays;
    }

    public function calculateAmoundDays($beginDate, $endDate, $libraryUnitId, $yearForOccursallyear=NULL)
    {
        $this->clear();
        
        if ( !$yearForOccursallyear )
        {
            $this->setTables('gtcHoliday H');
            $this->setWhere('occursallyear IS FALSE');
        }
        else
        {
            $this->setTables("(SELECT holidayid, 
                                      (TO_CHAR(date, 'DD/MM') || '/' || '{$yearForOccursallyear}')::date as date,
                                      libraryUnitId
                                FROM gtcholiday 
                               WHERE occursallyear IS TRUE) H");
        }

        $this->setColumns('COUNT(distinct(H.date))');
        $this->setWhere('H.date BETWEEN ? AND ?');
        $args[] = $beginDate;
        $args[] = $endDate;

        //Obtem somente feriados que não caiam no mesmo dia em que a bibliteca estiver fechada #12580
        $this->setWhere('(H.libraryUnitId = ? 
                       OR H.libraryUnitId is null)
                      AND EXTRACT (DOW FROM H.date) NOT IN (SELECT CASE weekdayid WHEN 7 THEN 0 ELSE weekdayid END 
                                                              FROM gtclibraryunitisclosed WHERE libraryUnitId = ?)');
        $args[] = $libraryUnitId;
        $args[] = $libraryUnitId;
       
        $rs = $this->query( $this->select($args));
        return $rs[0][0];
    }

    /**
     * Return true or false id passed day is a holyday
     *
     * @param $date date as yyyy-mm-dd format (Y-m-d)
     * @param $libraryUnitId the library to verifies the holiday, if you not pass the libraryUnitId the system will consider all units
     *
     */
    public function isHoliday($date, $libraryUnitId = NULL)
    {
        //verifica se dia está no meio de algum feriado prolongado
        $endDate = $this->checkHolidayEndDate($date, $libraryUnitId);
        if (isset($endDate))
        {
            return true;
        }

        //verifies if has exactely date
        $this->clear();
        $this->setColumns('holidayId,
                            date,
                            endDate,
                            description ,
                            occursAllYear,
                            libraryUnitId');
        $this->setTables($this->tables);
        $this->setWhere('date = ?');
        $args[] = $date;
        
        if ( !$libraryUnitId )
        {
            $this->setWhere('libraryUnitId is null'); //todos
        }
        else
        {
            $this->setWhere('coalesce(libraryunitid = ? , libraryunitid is null)'); //todos e o do unidade
            $args[] = $libraryUnitId;
        }
        $sql = $this->select($args);
        $rs = $this->query($sql, true);
        if ( $rs )
        {
            return true;
        }
        else // if not find date, try to search in all years
        {
            $this->clear();
            $this->setColumns($this->columns);
            $this->setTables($this->tables);
            $this->setWhere('occursAllYear = true');

            if ( $libraryUnitId )
            {
                $this->setWhere('( libraryunitid is null or libraryunitid = ? )');
                $sql = $this->select($libraryUnitId);
            }
            else
            {
                $this->setWhere('libraryunitid is null');
                $sql = $this->select();
            }

            $rs = $this->query($sql, true);
            if ( $rs )
            {
                foreach ( $rs as $line => $holiday )
                {
                    $dateH = new GDate($holiday->date);
                    $date = new GDate($date);

                    if ( ( $dateH->getDay() == $date->getDay() ) && ($dateH->getMonth() == $date->getMonth() ) )
                    {
                        return true;
                    }
                    if ( !is_null($holiday->endDate) && ($dateH->getMonth() == $date->getMonth() ))
                    {
                        $endDateH = new GDate ($holiday->endDate);
                        if ( intval($date->getDay()) > intval($dateH->getDay()) && intval($date->getDay()) < intval($endDateH->getDay()) )
                        {
                            return true;
                        }
                        if ( intval($date->getDay()) == intval($endDateH->getDay()) ) 
                        {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    /**
     * This functions verifies the date you want do pass, it verifies if is holyday or if the library is closed, in case of loan you must pass the forecast date, the system will not add days directely, it will only add if has holyday or library is closed.
     *
     * Take in mind that if don't has holyday, or don't is closed, the returned date is the same you pass.
     *
     * @param $timestampUnix the timestamp date you pass to system verifies
     * @param additionalDays days to add if the date if holiday.
     * @param libraryUnitId the library code the verifies the date
     *
     * @return return a timestamp the is the date that can be used in the place that you passed
     *
     * */
    public function checkHolidayDate($timestampUnix, $additionalDays, $libraryUnitId = NULL)
    {
        $MIOLO = MIOLO::getInstance();
        $module = $MIOLO->getCurrentModule();
        $dayOfWeek = date('N', $timestampUnix);

        $date = new GDate($timestampUnix);

        if ( $libraryUnitId )
        {
            $busLibraryUnitIsClosed = $this->MIOLO->getBusiness($this->module, 'BusLibraryUnitIsClosed');
            $isClosed = $busLibraryUnitIsClosed->getLibraryUnitIsClosed($libraryUnitId, $dayOfWeek);

            if ( $isClosed )
            {
                $date->addDay(1);
                $timestampUnix = $date->getTimestampUnix();
                return $this->checkHolidayDate($timestampUnix, $additionalDays, $libraryUnitId);
                ;
            }
        }

        $isHoliday = $this->isHoliday($date->getDate(GDate::MASK_DATE_DB), $libraryUnitId);
        
        if ( $isHoliday && $additionalDays != '0' && !is_null($additionalDays))
        {
            $endDate = $this->checkHolidayEndDate($date, $libraryUnitId);
            // if para verificar se existe data de fim
            if (isset($endDate))
            {
                $endDate = new GDate($endDate);
                $endDate = $endDate->getTimestampUnix();
                $countHolidayDays = $endDate - $date->getTimestampUnix();
                $countHolidayDays = $countHolidayDays / 60 / 60 / 24; //dias que vai estar fechado
                $countHolidayDays = ceil($countHolidayDays);   //arredonda dias para cima
                if ($countHolidayDays > $additionalDays)
                {
                    $date->addDay(intval($countHolidayDays)+1); //adiciona 1 para cair um dia depois do feriado 
                    $timestampUnix = $date->getTimestampUnix();
                    return $this->checkHolidayDate($timestampUnix, $additionalDays, $libraryUnitId);
                }
            }
            $date->addDay($additionalDays);
            $timestampUnix = $date->getTimestampUnix();

            return $this->checkHolidayDate($timestampUnix, $additionalDays, $libraryUnitId);
        }

        return $timestampUnix;
    }

    /**
     * Verifica feriados e dias fechados para a data
     * 
     * @param GDate $initialDate
     * @param integer $additionalDays
     * @param integer $libraryUnitId
     * @return GDate 
     */
    public function checkHolidayBetweenDate($initialDate, $additionalDays, $libraryUnitId)
    {
        /*
          endDate = Soma a initialData com o additionDays
          Verfica se existem feriados e dias que a biblioteca está fechada entre a data initialDate e endDate
          $feriados = data <= endDate and data > initalDate
          $fechada = analizar se a bibliteca tá fechada desconsiderando o dia atual
          Se a feriado e fechado forem nulo, retorna o endDate;
          Else. Verifcar quantos feriados e fechado cuidando para mesclar os dois
          chama a funcao de novo e passa o endDate, quandidadeDeFeriadoFechada */

        if ( $initialDate instanceof GDate )
        {
            $date = $initialDate;
        }
        else
        {
            $date = new GDate($initialDate);
        }

        $endDate = clone( $date );
        $endDate->addDay($additionalDays);
        $count = 0;

        for ( $i = 0; $i < $additionalDays; $i++ )
        {
            $date->addDay(1);
            $isHoliday = $this->isHoliday($date->generate(), $libraryUnitId);
            $isClosed = $this->busLibraryUnitIsClosed->isClosed($libraryUnitId, $date->getDayOfWeek());

            if ( $isHoliday || $isClosed )
            {
                $count++;
            }
        }

        if ( $count )
        {
            return $this->checkHolidayBetweenDate($endDate, $count, $libraryUnitId);
        }

        return $endDate->generate();
    }

    /**
     * Checks if the date passed as parameter is within a extended holiday.
     * Return the end date of this holiday
     * 
     * @param GDate $date
     * @param integer $libraryUnitId
     * @param boolean $param
     * @param GDate $occursallyear
     * @param GDate $enddate
     * @return date
     * 
     */
    public function checkHolidayEndDate ($date, $libraryUnitId = NULL, $param = FALSE, $occursallyear = NULL, $endDate = NULL)
    {
        // vai retornar a data final do feriado
        // sql utilizada para saber a data de devolução prevista
        if ($param == FALSE)
        {
            $this->clear();
            $this->setColumns('enddate');
            $this->setTables('gtcholiday');
            $this->setWhere(' ? BETWEEN date AND enddate 
                           AND (libraryunitid = ? OR libraryunitid IS NULL)
                           AND  enddate IS NOT NULL');
            $args[] = $date;
            $args[] = $libraryUnitId;

            $sql = $this->select($args);

            $rs = $this->query($sql);
            return $rs[0][0];
        }

        // sql que recebe uma data e verifica se dentro da data inicial e final de feriado essa data esteja presente 
        // sql utilizada para saber se dia cai no meio de um feriado
        if ($param === TRUE)
        {
            if($occursallyear === NULL)
            {
                $this->clear();
                $this->setColumns('date, enddate');
                $this->setTables('gtcholiday');
                $this->setWhere('( ? BETWEEN date AND enddate OR date = ? ) 
                               AND (libraryunitid = ? OR libraryunitid IS NULL)
                               ORDER BY date, enddate
                               LIMIT 1');
                $args[] = $date;
                $args[] = $date;
                $args[] = $libraryUnitId;
    
                $sql = $this->select($args);
    
                $rs = $this->query($sql);
                return $rs[0][0];
            }
        }

        // sql que recebe uma data e verifica se dentro da data inicial e final de feriado essa data esteja presente 
        // vai tambem verificar se dia cai no meio de um feriado 
        // mas função onde passa os parametros está levando em consideração feriados que ocorrem todos os anos
        if (isset($occursallyear))
        {
            $this->clear();
            $this->setColumns('TRUE');
            $this->setTables('gtcholiday');
            $this->setWhere('( ? BETWEEN ? AND ?) 
                           AND (libraryunitid = ? OR libraryunitid IS NULL)
                           ORDER BY date, enddate
                           LIMIT 1');
            $args[] = $occursallyear;
            $args[] = $date;
            $args[] = $endDate;
            $args[] = $libraryUnitId;

            $sql = $this->select($args);
            $rs = $this->query($sql);
            return $rs[0][0];
        }        
    }

    /**
     * Vai trazer a quantidade de dias de feriado que existem entre as datas $beginDate e $endDateHoliday.
     * Ele vai considerar os dias em que a biblioteca está fechada e descontar esses dias do valor total de $days.
     * 
     * @param GDate $begindate
     * @param GDate $enddate
     * @param integer $libraryUnitId
     * @param integer $day
     * @param boolean $param
     * @return integer $day
     * 
     */
    public function getHolidaysInTheFine ($beginDate, $endDate, $libraryUnitId, $day = 0, $param = FALSE)
    {
        $compare = TRUE;
        $beginDate = new GDate($beginDate);
        $endDate = new Gdate($endDate);
        $endDateHoliday = $this->checkHolidayEndDate($beginDate, $libraryUnitId, TRUE);
        // Verifica as datas que são de todos os anos, só vai entrar nesse if na primeira vez que chama a função
        if ($param == FALSE)
        {
            $dates = array();
            $datas = $this->getOccursallYear();
            foreach ($datas as $a)
            {
                $a = new GDate($a[0]);
                if ($a->getYear() != $beginDate->getYear())
                {
                    $a->setYear($beginDate->getYear());
                }
                $dateHoliday = $this->checkHolidayEndDate($beginDate->generateToDB(), $libraryUnitId, TRUE, $a->generateToDB(), $endDate->generateToDB());
                if (isset($dateHoliday))
                {
                    $dates[] = $a;
                    $day += 1;
                    $closed = $a->getDayOfWeek();
                    if ($this->CheckDayOfTheWeek($closed))
                    {
                        $day -= 1;  
                    }
                }
            }
            foreach ($dates as $a)
            {
                if ($beginDate->__toString()== $a->__toString())
                {
                    $compare = FALSE;
                }
            }
        }
        if (isset($endDateHoliday))
        {
            if ($compare == TRUE)
            {
                $day += 1;
                $closed = $beginDate->getDayOfWeek();
                if ($this->CheckDayOfTheWeek($closed))
                {
                    $day -= 1;  
                }
            }
        }
        $beginDate->addDay(1);
        if ($endDate->__toString() == $beginDate->__toString())
        {
            $day = intval($day);
            return $day;
        }
        return $this->getHolidaysInTheFine($beginDate, $endDate, $libraryUnitId, $day, TRUE);
    }

    /**
     * Vai retornar true caso dia da semana passada como parâmetro seja um dia em que a biblioteca esteja fechada
     * 
     * @param GDate $date
     * @return TRUE
     * 
     */
    public function CheckDayOfTheWeek ($date)
    {
        $this->clear();
        $this->setColumns('TRUE');
        $this->setTables('gtclibraryunitisclosed');
        $this->setWhere(' weekdayid = ?');
            
        $args[] = $date;

        $sql = $this->select($args);

        $rs = $this->query($sql);
        $param = $rs[0][0];
        return $param;
    }
    
    /**
     * Vai retonar um array com os feriados que são de todos os anos
     * 
     * @return array
     * 
     */
    public function getOccursallYear ()
    {
        $this->clear();
        $this->setColumns('date');
        $this->setTables('gtcholiday');
        $this->setWhere(' occursallyear = TRUE');
            
        $args[] = $date;

        $sql = $this->select($args);

        $rs = $this->query($sql);
        return $rs;
    }
}
?>
