<?php

/**
 * @package        ARTIO Booking
 * @subpackage        models
 * @copyright        Copyright (C) 2014 ARTIO s.r.o.. All rights reserved.
 * @author            ARTIO s.r.o., http://www.artio.net
 * @license        GNU/GPL http://www.gnu.org/copyleft/gpl.html
 * @link            http://www.artio.net Official website
 */
defined('_JEXEC') or die;

class BookingModelOverview extends JModelLegacy
{

    /**
     * @var JRegistry
     */
    private $params;
    private $hourlyParents;
    private $current;
    private $children;
    private $navigator;

    /**
     * Get item parents which children have hourly reservation type.
     *
     * @return array
     */
    public function getHourlyParents()
    {
        if (empty($this->hourlyParents)) {
            $db = $this->getDbo();
            $query = $db->getQuery(true);
            $date = JFactory::getDate();
            $user = JFactory::getUser();

            $now = $db->q($date->toSql());
            $null = $db->q($db->getNullDate());
            $access = $user->getAuthorisedViewLevels();

            $query->select('s.id, s.title, s.alias')
                ->from('#__booking_subject AS s')
                ->leftJoin('#__booking_subject AS c ON s.id = c.parent')
                ->where('c.id IS NOT NULL')
                ->where('s.state = ' . SUBJECT_STATE_PUBLISHED)
                ->where('s.access IN (' . implode(', ', $access) . ')')
                ->where('(s.publish_up <= ' . $now . ' OR s.publish_up = ' . $null . ')')
                ->where('(s.publish_down >= ' . $now . ' OR s.publish_down = ' . $null . ')')
                ->where('c.state = ' . SUBJECT_STATE_PUBLISHED)
                ->where('c.access IN (' . implode(', ', $access) . ')')
                ->where('(c.publish_up <= ' . $now . ' OR c.publish_up = ' . $null . ')')
                ->where('(c.publish_down >= ' . $now . ' OR c.publish_down = ' . $null . ')')
                ->order('s.ordering')
                ->group('s.id');

            $this->hourlyParents = $db->setQuery($query)->loadObjectList();
        }
        return $this->hourlyParents;
    }

    /**
     * Get current parent for hourly overview.
     *
     * @return stdClass
     */
    public function getHourlyCurrent()
    {
        $app = JFactory::getApplication();

        $this->getHourlyParents();

        $cid = JArrayHelper::getColumn($this->hourlyParents, 'id');
        $default = reset($cid);

        $id = $app->getUserStateFromRequest('com_booking.overview.id', 'id', $default, 'int');

        foreach ($this->hourlyParents as $i => $candidate) {
            if ($candidate->id == $id) {
                unset($this->hourlyParents[$i]);
                $this->current = $candidate;
            }
        }

        if (empty($this->current)) { // system has no parent items
            $this->current = new stdClass();
            $this->current->id = $this->current->title = $this->current->alias = null; // root
        }

        return $this->current;
    }

    /**
     * Get data for day navigator.
     *
     * @return stdClass
     */
    public function getDayNavigator()
    {
        if (empty($this->navigator)) {
            $app = JFactory::getApplication();
            $params = $this->getParams();
            $config = AFactory::getConfig();

            $this->navigator = new stdClass();

            $date = JFactory::getDate($app->getUserStateFromRequest('com_booking.overview.date', 'date', 'now', 'string'));

            $this->navigator->currentDate = $date->format('Y-m-d');
            $this->navigator->currentWeek = $date->format('W');
            $this->navigator->currentDay = $date->format(ADATE_FORMAT_NORMAL);
            $this->navigator->currentMonth = $date->format('F Y');

            $this->navigator->prevDay = $this->modify($date->toUnix(), '- 1 day', 'Y-m-d');
            $this->navigator->nextDay = $this->modify($date->toUnix(), '+ 1 day', 'Y-m-d');

            $this->navigator->lastWeekDay = $this->modify($date->toUnix(), ($config->firstDaySunday ? 'this week saturday' : 'this week sunday'), 'Y-m-d');
            $this->navigator->firstWeekDay = $this->modify($date->toUnix(), ($config->firstDaySunday ? 'this week sunday' : 'this week monday'), 'Y-m-d');

            $this->navigator->prevWeek = $this->modify($date->toUnix(), '- 1 week', 'Y-m-d');
            $this->navigator->nextWeek = $this->modify($date->toUnix(), '+ 1 week', 'Y-m-d');

            $this->navigator->prevMonth = $this->modify($date->toUnix(), '- 1 month', 'Y-m-01');
            $this->navigator->nextMonth = $this->modify($date->toUnix(), '+ 1 month', 'Y-m-01');
        }
        return $this->navigator;
    }

    private function modify($date, $modify, $format)
    {
        $date = JFactory::getDate($date);
        $date->modify($modify);
        return $date->format($format);
    }

    /**
     * Get current parent children.
     *
     * @return array
     */
    public function getChildren()
    {
        if (empty($this->children)) {
            $db = $this->getDbo();
            $query = $db->getQuery(true);
            $date = JFactory::getDate();
            $user = JFactory::getUser();

            $now = $db->q($date->toSql());
            $null = $db->q($db->getNullDate());
            $access = $user->getAuthorisedViewLevels();

            $query->select('s.id, s.title, s.alias')
                ->from('#__booking_subject AS s')
                ->where('s.state = ' . SUBJECT_STATE_PUBLISHED)
                ->where('s.access IN (' . implode(', ', $access) . ')')
                ->where('(s.publish_up <= ' . $now . ' OR s.publish_up = ' . $null . ')')
                ->where('(s.publish_down >= ' . $now . ' OR s.publish_down = ' . $null . ')')
                ->where('s.parent = ' . (int)$this->current->id)
                ->group('s.id')
                ->order('s.ordering');

            $this->children = $db->setQuery($query)->loadObjectList();
        }
        return $this->children;
    }

    /**
     * Get schedule of hourly overview.
     *
     * @return array
     */
    public function getHourlySchedule()
    {
        $db = $this->getDbo();
        $query = $db->getQuery(true);
        $date = JFactory::getDate();
        $user = JFactory::getUser();
        $params = $this->getParams();

        if ($params->get('schedule_type', 2) == 1) {
            $now = $db->q($date->toSql());
            $null = $db->q($db->getNullDate());
            $access = $user->getAuthorisedViewLevels();

            $query->select('MIN(p.time_up) AS up, MAX(p.time_down) AS down')
                ->from('#__booking_subject AS s')
                ->leftJoin('#__booking_price AS p ON s.id = p.subject')
                ->leftJoin('#__booking_reservation_type AS t ON t.id = p.rezervation_type')
                ->where('s.state = ' . SUBJECT_STATE_PUBLISHED)
                ->where('s.access IN (' . implode(', ', $access) . ')')
                ->where('(s.publish_up <= ' . $now . ' OR s.publish_up = ' . $null . ')')
                ->where('(s.publish_down >= ' . $now . ' OR s.publish_down = ' . $null . ')')
                ->where('s.parent = ' . (int)$this->current->id)
                ->where('t.type = ' . RESERVATION_TYPE_HOURLY);

            $limit = $db->setQuery($query)->loadObject();
            if (!$limit) {
                $array = array('up' => '00:00:00', 'down' => '24:00:00');
                $limit = JArrayHelper::toObject($array);
            }
            if ($limit->down == '00:00:00') {
                $limit->down = '24:00:00';
            }
            $limit->up = floor(str_replace(':', '.', $limit->up));
            $limit->down = ceil(str_replace(':', '.', $limit->down));
        } else {
            $begin = $params->get('manually_schedule_begin', 0);
            $end = $params->get('manually_schedule_end', 23);
            $limit = array('up' => $begin, 'down' => $end);
            $limit = JArrayHelper::toObject($limit);
        }

        $schedule = array();

        for ($hour = $limit->up; $hour <= $limit->down; $hour++) {
            $date->setTime($hour, 0, 0);
            $schedule[] = $date->format(ATIME_FORMAT);
        }

        return $schedule;
    }

    /**
     * Get days of the current week.
     *
     * @return array
     */
    public function getWeekSchedule()
    {
        $navigator = $this->getDayNavigator();
        $days = array();

        $begin = JFactory::getDate($navigator->firstWeekDay);
        $end = JFactory::getDate($navigator->lastWeekDay);

        for ($day = $begin; $day->toUnix() <= $end->toUnix(); null) {
            $days[] = $day->format('Y-m-d');
            $day->modify('+ 1 day');
        }

        return $days;
    }

    /**
     * Get days of the current month.
     *
     * @return JDate[]
     */
    public function getDailySchedule()
    {
        $day = JFactory::getDate($this->navigator->currentDate);
        $day->modify('first day of this month');
        $days = array();
        do {
            $days[] = clone $day;
            $day->modify('+ 1 day');
        } while ($this->navigator->currentMonth == $day->format('F Y'));
        return $days;
    }

    /**
     * Get current schedule.
     *
     * @return JDate[]
     * @throws Exception
     */
    public function getCurrentSchedule()
    {
        static $days = array();

        if (!$days) {
            $params = $this->getParams();
            $navigator = $this->getDayNavigator();

            $day = JFactory::getDate($navigator->currentDate);
            /* @var $day JDate */
            $day->modify('last ' . $params->get('starts_at'));

            $count = intval($params->get('number_of_days'));

            for ($i = 0; $i < $count; $i++) {
                $days[] = clone $day;
                $day->modify('+ 1 day');
            }
        }
        return $days;
    }

    /**
     * Get date of current week according to configuration.
     *
     * @return JDate
     * @throws Exception
     */
    public function getCurrentWeekDate()
    {
        $params = $this->getParams();
        $days = $this->getCurrentSchedule();
        $date = intval($params->get('show_date_for'));

        foreach ($days as $day) {
            if ($date === intval($day->format('w'))) {
                return $day;
            }
        }

        return reset($days);
    }

    /**
     * Get reserved reservations for current day and current family.
     *
     * @return array
     */
    public function getHourlyReservations()
    {
        $db = $this->getDbo();
        $query = $db->getQuery(true);
        $navigator = $this->getDayNavigator();
        $params = $this->getParams();
        $singleWeek = $this->getState('week');
        $config = AFactory::getConfig();

        if (!$singleWeek) {
            $date = JFactory::getDate($navigator->currentDate);
            $up = $db->q($date->format('Y-m-d 00:00:00'));
            $down = $db->q($date->format('Y-m-d 23:59:59'));
        } else {
            $up = $db->q(JFactory::getDate($navigator->firstWeekDay)->format('Y-m-d 00:00:00'));
            $down = $db->q(JFactory::getDate($navigator->lastWeekDay)->format('Y-m-d 23:59:59'));
        }

        $ids = JArrayHelper::getColumn($this->children, 'id');

        if (empty($ids)) {
            return array();
        }

        $query->select('i.subject AS id, COALESCE(p.from, i.from) AS up, COALESCE(p.to, i.to) AS down, r.title_before, r.firstname, r.middlename, r.surname, r.title_after, r.id AS rid, i.message, r.note, i.subject')
            ->from('#__booking_reservation_items AS i')
            ->leftJoin('#__booking_reservation AS r ON i.reservation_id = r.id')
            ->leftJoin('#__booking_reservation_period AS p ON p.reservation_item_id = i.id')
            ->where('i.subject IN (' . implode(', ', $ids) . ')');
        if ($config->confirmReservation < 2) {
            $query->where('r.state IN (' . RESERVATION_ACTIVE . ',' . RESERVATION_PRERESERVED . ')');
        } else {
            $query->where('r.state = ' . RESERVATION_ACTIVE);
        }
        $query->order('up')
            ->having('up <=' . $down)
            ->having('down >= ' . $up);

        $reservations = $db->setQuery($query)->loadObjectList();
        $grouped = array();

        foreach ($reservations as $reservation) {
            $reservation->upDate = JString::substr($reservation->up, 0, 10);
            $reservation->downDate = JString::substr($reservation->down, 0, 10);
            if (!isset($grouped[$reservation->id])) {
                $grouped[$reservation->id] = array();
            }
            $grouped[$reservation->id][] = $reservation;
        }

        return $grouped;
    }

    /**
     * Get reserved reservations for current day and current family.
     *
     * @return array
     */
    public function getDailyReservations(JDate $begin = null, JDate $end = null)
    {
        $db = $this->getDbo();
        $query = $db->getQuery(true);
        $navigator = $this->getDayNavigator();
        $params = $this->getParams();
        $singleWeek = $this->getState('week');
        $config = AFactory::getConfig();

        $day = JFactory::getDate($this->navigator->currentDate);
        if ($begin instanceof JDate) {
            $up = $db->q($begin->format('Y-m-d 00:00:00'));
        } else {
            $begin = $day;
            $up = $db->q($day->format('Y-m-01 00:00:00'));
        }
        if ($end instanceof JDate) {
            $down = $db->q($end->format('Y-m-d 23:59:59'));
        } else {
            $begin = $day;
            $down = $db->q($day->format('Y-m-t 23:59:59'));
        }

        $ids = JArrayHelper::getColumn($this->children, 'id');

        if (empty($ids)) {
            return array();
        }

        $query->select('i.subject AS id, COALESCE(p.from, i.from) AS up, COALESCE(p.to, i.to) AS down, r.title_before, r.firstname, r.middlename, r.surname, r.title_after, r.id AS rid, i.message, r.note, i.subject, i.id AS iid')
            ->from('#__booking_reservation_items AS i')
            ->leftJoin('#__booking_reservation AS r ON i.reservation_id = r.id')
            ->leftJoin('#__booking_reservation_period AS p ON p.reservation_item_id = i.id')
            ->where('i.subject IN (' . implode(', ', $ids) . ')');
        if ($config->confirmReservation < 2) {
            $query->where('r.state IN (' . RESERVATION_ACTIVE . ',' . RESERVATION_PRERESERVED . ')');
        } else {
            $query->where('r.state = ' . RESERVATION_ACTIVE);
        }
        $query->order('up')
            ->having('up <=' . $down)
            ->having('down >= ' . $up);

        $reservations = $db->setQuery($query)->loadObjectList();
        $grouped = array();

        $this->addSupplements($reservations);

        foreach ($reservations as $reservation) {
            $reservation->upDate = JString::substr($reservation->up, 0, 10);
            $reservation->downDate = JString::substr($reservation->down, 0, 10);
            $reservation->begin = $begin->diff(JFactory::getDate($reservation->up))->d;
            $reservation->end = $begin->diff(JFactory::getDate($reservation->down))->d;
            if (!isset($grouped[$reservation->id])) {
                $grouped[$reservation->id] = array();
            }
            $grouped[$reservation->id][] = $reservation;
        }

        return $grouped;
    }

    public function getCurrentReservations()
    {
        $current = $this->getCurrentSchedule();
        return $this->getDailyReservations(reset($current), end($current));
    }

    /**
     * Get current menu item configuration.
     *
     * @return JRegistry
     * @throws Exception
     */
    public function getParams()
    {
        if (!$this->params) {
            $active = JFactory::getApplication()->getMenu()->getActive();
            if ($active) {
                $this->params = $active->params;
            } else {
                $this->params = new JRegistry();
            }
        }
        return $this->params;
    }

    /**
     * Add supplements to show into reservation items.
     *
     * @param stdClass[] $reservations
     * @return $this
     * @throws Exception
     */
    protected function addSupplements($reservations) {
        $items = $this->getShowSupplements();

        if ($reservations && $items) {
            $ids = array();

            foreach ($reservations as $reservation) {
                $reservation->supplements = array();

                foreach ($items as $item) { // set default supplement values
                    $reservation->supplements[$item->alias] = $item->alias . $this->modifySupplementValue($item, 0);
                }

                $ids[$reservation->iid] = $reservation;
            }

            $query = $this->_db->getQuery(true)
                ->select('reservation, title, value, capacity')
                ->from('#__booking_reservation_supplement')
                ->where('reservation IN (' . implode(', ', array_map('intval', array_keys($ids))) . ')')
                ->where('title IN (' . implode(', ', array_map(array($this->_db, 'q'), array_keys($items))) . ')');

            $supplements = $this->_db->setQuery($query)->loadObjectList();

            foreach ($supplements as $supplement) {
                $item = $items[$supplement->title];

                if (isset($supplement->{$item->data})) {
                    $supplement->{$item->data} = $this->modifySupplementValue($item, $supplement->{$item->data});

                    $ids[$supplement->reservation]->supplements[$item->alias] = $item->alias . $supplement->{$item->data}; // show supplement alias and data
                }
            }
        }

        return $this;
    }

    /**
     * Modify supplement value by configuration.
     *
     * @param JObject $item
     * @param string $value
     * @return string
     */
    protected function modifySupplementValue(JObject $item, $value)
    {
        if ($item->operator && $item->modifier) {
            eval("\$value {$item->operator}= {$item->modifier};");
        }
        return $value;
    }

    /**
     * Get configuration to show supplements in time lines.
     *
     * @return array
     * @throws Exception
     */
    public function getShowSupplements() {
        $items = array_filter(array_map('trim', explode("\n", $this->getParams()->get('show_supplements')))); // split text to rows

        foreach ($items as $i => $item) {
            unset($items[$i]);

            $item = array_filter(array_map('trim', explode(';', $item))); // split row to cels separated by ;

            if ($item) {
                $data = isset($item[2]) ? $item[2] : 'capacity';

                $operator = $modifier = $match = null;
                if (preg_match('/([a-zA-Z0-9_-]+)([+-])([1-9]+[0-9]*)/', $data, $match)) { // for example Beds+1
                    $data = $match[1]; // supplement value or capacity
                    $operator = $match[2]; // operator + or - to increment or decrement supplement value
                    $modifier = intval($match[3]); // modifier value to increment or decrement supplement value
                }

                $items[$item[0]] = new JObject(array(
                    'title' => $item[0], // supplement title
                    'alias' => isset($item[1]) ? $item[1] : $item[0], // title alias to show
                    'data' => $data, // data to show (value or capacity)
                    'operator' => $operator,
                    'modifier' => $modifier));
            }
        }

        return $items;
    }
}
