<?php

class ACMS_GET_Entry_Calendar extends ACMS_GET
{
    protected $config;
    protected $entries;

    protected $ymd;
    protected $y;
    protected $m;
    protected $d;

    /**
     * カレンダーの開始日
     * 例）前後月のエントリー表示を有効にしている場合、2022/06 のURLコンテキストでアクセスしたとき 2022-05-30 00:00:00
     *
     * @var string
     */
    protected $startDate;

    /**
     * カレンダーの終了日
     * 例）前後月のエントリー表示を有効にしている場合、2022/06 のURLコンテキストでアクセスしたとき 2022-07-03 23:59:59
     *
     * @var string
     */
    protected $endDate;

    /**
     * エントリーの開始日
     *
     * @var string
     */
    protected $entryStartDate;

    /**
     * エントリーの終了日
     *
     * @var string
     */
    protected $entryEndDate;

    /**
     * day:loop ブロックの開始日
     *
     * @var int
     */
    protected $firstDay;

    /**
     * day:loop ブロックの終了日
     *
     * @var int
     */
    protected $lastDay;

    /**
     * day:loop ブロックのループ回数
     *
     * @var int
     */
    protected $loopCount;

    /**
     * day:loop ブロックの最初の曜日（数値）
     *
     * @var int
     */
    protected $firstW;

    /**
     * 前後月を考慮した最初の曜日（数値）
     *
     * @var int
     */
    protected $beginW;

    var $_axis  = array(
        'bid'   => 'self',
        'cid'   => 'self'
    );

    var $_scope = array(
        'date'  => 'global',
        'start' => 'global',
        'end'   => 'global'
    );

    /**
     * コンフィグの取得
     *
     * @return array
     */
    protected function initVars()
    {
        return array(
            'mode' => config('entry_calendar_mode'),
            'pagerCount' => config('entry_calendar_pager_count'),
            'order' => config('entry_calendar_order'),
            'aroundEntry' => config('entry_calendar_around'),
            'beginWeek' => config('entry_calendar_begin_week'),
            'maxEntryCount' => config('entry_calendar_max_entry_count'),
            'weekLabels' => configArray('entry_calendar_week_label'),
            'today' => config('entry_calendar_today')
        );
    }

    /**
     * 日付のURLコンテキストに関わる変数の代入
     *
     * @return void
     */
    protected function setDateContextVars()
    {
        $this->ymd = substr($this->start, 0, 10) === '1000-01-01'
                ? date('Y-m-d', requestTime())
                : substr($this->start, 0, 10);

        list($this->y, $this->m, $this->d) = explode('-', $this->ymd);
    }

    /**
     * カレンダーの計算に必要な変数の代入
     *
     * @return void
     */
    protected function setCalendarVars()
    {
        switch ($this->config['mode']) {
            case "month":
                $ym = substr($this->ymd, 0, 7);
                $this->startDate = $ym . '-01 00:00:00';
                $this->endDate = $ym . '-31 23:59:59';

                $this->entryStartDate = $this->startDate;
                $this->entryEndDate = $this->endDate;

                $this->firstDay = 1;
                $this->lastDay = intval(date('t', strtotime($ym . '-01')));
                $this->loopCount = $this->lastDay;

                $this->firstW = intval(date('w', strtotime($ym . '-01')));
                $this->beginW = intval($this->config['beginWeek']);
                $prevS = ($this->firstW + (7 - $this->beginW)) % 7;

                $this->startDate = $this->computeDate(
                    (int)substr($this->startDate, 0, 4),
                    (int)substr($this->startDate, 5, 2),
                    (int)substr($this->startDate, 8, 2),
                    -$prevS
                );
                $this->startDate = $this->startDate . ' 00:00:00';

                $lastW = intval(date('w', strtotime($this->endDate)));
                $nextS = 6 - ($lastW + (7 - $this->beginW)) % 7;
                $this->endDate = $this->computeDate(
                    (int)substr($this->endDate, 0, 4),
                    (int)substr($this->endDate, 5, 2),
                    (int)substr($this->endDate, 8, 2),
                    $nextS
                );
                $this->endDate = $this->endDate . ' 23:59:59';

                if ($this->config['aroundEntry'] === 'on') {
                    $this->entryStartDate = $this->startDate;
                    $this->entryEndDate = $this->endDate;
                }

                $this->firstW = intval(date('w', strtotime($ym . '-01')));
                $this->beginW = intval($this->config['beginWeek']);

                break;

            case "week":
                $f_week = intval(date('w', strtotime($this->ymd)));
                $prev_num = ($f_week >= $this->config['beginWeek']) ? $f_week - $this->config['beginWeek'] : 7 - ($this->config['beginWeek'] - $f_week);
                $minus_day = $this->computeDate(
                    (int)substr($this->ymd, 0, 4),
                    (int)substr($this->ymd, 5, 2),
                    (int)substr($this->ymd, 8, 2),
                    -$prev_num
                );
                $add_day = $this->computeDate(
                    (int)substr($this->ymd, 0, 4),
                    (int)substr($this->ymd, 5, 2),
                    (int)substr($this->ymd, 8, 2),
                    6 - $prev_num
                );

                $this->startDate = $minus_day . ' 00:00:00';
                $this->endDate = $add_day . ' 23:59:59';

                $this->firstDay = substr($minus_day, 8, 2);
                $this->lastDay = intval(date('t', strtotime($this->startDate)));

                $this->entryStartDate = $this->startDate;
                $this->entryEndDate = $this->endDate;

                $this->loopCount = 7;

                $this->firstW = intval(date('w', strtotime($this->startDate)));
                $this->beginW = intval($this->config['beginWeek']);

                break;

            case "days":
                $add_day = $this->computeDate(
                    (int)substr($this->ymd, 0, 4),
                    (int)substr($this->ymd, 5, 2),
                    (int)substr($this->ymd, 8, 2),
                    6
                );
                $this->startDate = $this->ymd . ' 00:00:00';
                $this->endDate = $add_day . ' 23:59:59';

                $this->firstDay = substr($this->ymd, 8, 2);
                $this->lastDay = intval(date('t', strtotime($this->startDate)));

                $this->loopCount = 7;

                $this->entryStartDate = $this->startDate;
                $this->entryEndDate = $this->endDate;

                $this->firstW = intval(date('w', strtotime($this->startDate)));
                $this->beginW = intval(date('w', strtotime($this->startDate)));

                break;
        }
    }

    function get()
    {
        if (!$this->setConfig()) return '';

        $DB = DB::singleton(dsn());
        $Tpl = new Template($this->tpl, new ACMS_Corrector());

        $this->buildModuleField($Tpl);

        $this->setDateContextVars();
        $this->setCalendarVars();

        $q = $this->buildQuery();
        $this->entries = $DB->query($q, 'all');

        $this->buildWeekLabel($Tpl);

        if ($this->config['mode'] === 'month') {
            // spacer
            $this->buildForeSpacer($Tpl);
        }

        $this->buildDays($Tpl);

        if ($this->config['mode'] === 'month') {
            // spacer
            $this->buildRearSpacer($Tpl);
        }

        $Tpl->add('date', $this->getDateVars());

        return $Tpl->get();
    }

    function computeDate($year, $month, $day, $add_days)
    {
        $base_sec = mktime(0, 0, 0, $month, $day, $year);
        $add_sec = $add_days * 86400;
        $target_sec = $base_sec + $add_sec;

        return date('Y-m-d', $target_sec);
    }

    /**
     * コンフィグのセット
     *
     * @return bool
     */
    protected function setConfig()
    {
        $this->config = $this->initVars();
        if ($this->config === false) {
            return false;
        }

        if(!is_numeric($this->config['pagerCount']) || intval($this->config['pagerCount']) == 0){
            $this->config['pagerCount'] = 7;
        }

        if(!is_numeric($this->config['maxEntryCount']) || $this->config['maxEntryCount'] == 0){
            $this->config['maxEntryCount'] = 3;
        }

        return true;
    }

    /**
     * sqlの組み立て
     *
     * @return SQL_Select
     */
    protected function buildQuery()
    {
        $SQL = SQL::newSelect('entry');
        $SQL->addSelect(SQL::newFunction('entry_datetime', array('SUBSTR', 0, 10)), 'entry_date', null, 'DISTINCT');
        $SQL->addSelect('entry_id');
        $SQL->addSelect('entry_approval');
        $SQL->addSelect('entry_title');
        $SQL->addSelect('entry_category_id');
        $SQL->addSelect('entry_blog_id');
        $SQL->addSelect('entry_link');
        $SQL->addSelect('entry_datetime');
        $SQL->addSelect('entry_start_datetime');
        $SQL->addSelect('entry_end_datetime');
        $SQL->addSelect('entry_status');
        $SQL->addLeftJoin('blog', 'blog_id', 'entry_blog_id');
        ACMS_Filter::blogTree($SQL, $this->bid, $this->blogAxis());
        ACMS_Filter::blogStatus($SQL);
        $SQL->addLeftJoin('category', 'category_id', 'entry_category_id');

        ACMS_Filter::categoryTree($SQL, $this->cid, $this->categoryAxis());
        ACMS_Filter::categoryStatus($SQL);
        ACMS_Filter::entrySession($SQL);
        ACMS_Filter::entrySpan($SQL, $this->entryStartDate, $this->entryEndDate);
        ACMS_Filter::entryOrder($SQL, $this->config['order'], $this->uid, $this->cid);

        return $SQL->get(dsn());
    }

    /**
     * weekLabelループブロックの組み立て
     *
     * @param Template & $Tpl
     * @return bool
     */
    protected function buildWeekLabel(&$Tpl)
    {
        for ($i = 0; $i < 7; $i++) {
            $w  = ($this->beginW + $i) % 7;
            if (!empty($this->config['weekLabels'][$w])) {
                $Tpl->add('weekLabel:loop', array(
                    'w'     => $w,
                    'label' => $this->config['weekLabels'][$w],
                ));
            }
        }

        return true;
    }

    /**
     * 前月のspacerの組み立て
     *
     * @param Template & $Tpl
     * @return bool
     */
    protected function buildForeSpacer(&$Tpl)
    {
        $span = ($this->firstW + (7 - $this->beginW)) % 7;
        $date = substr($this->startDate, 0, 10);

        if ($span) {
            for ($i=0; $i < $span; $i++) {
                $pw = ($this->beginW + $i) % 7;
                if (!empty($this->config['weekLabels'][$pw])) {
                    $this->buildEntries($Tpl, $date, 'foreEntry:loop', $this->config['aroundEntry'] === 'on');

                    $Tpl->add('foreSpacer', array(
                        'prevDay'   => intval(substr($date, 8, 2)), // 先頭の0削除
                        'w'         => $pw,
                        'week'      => $this->config['weekLabels'][$pw],
                    ));

                    $date = $this->computeDate((int)substr($date, 0, 4), (int)substr($date, 5, 2), (int)substr($date, 8, 2), 1);
                }
            }

            return true;
        }

        return false;
    }

    /**
     * 翌月のspacerの組み立て
     *
     * @param Template & $Tpl
     * @return bool
     */
    protected function buildRearSpacer(&$Tpl)
    {
        $lastW  = intval(date('w', strtotime('last day of' . substr($this->ymd, 0, 7))));
        $span = 6 - ($lastW + (7 - $this->beginW)) % 7;

        $date = substr($this->endDate, 0, 7) . '-01';

        if ($span) {
            $nws = ($this->beginW + (7 - $span)) % 7;
            for ($i = 0; $i < $span; $i++) {
                $nw = ($nws + $i) % 7;
                if (!empty($this->config['weekLabels'][$nw])) {
                    $this->buildEntries($Tpl, $date, 'rearEntry:loop', $this->config['aroundEntry'] === 'on');

                    $Tpl->add('rearSpacer', array(
                        'nextDay' => intval(substr($date, 8, 2)), // 先頭の0削除
                        'w'       => $nw,
                        'week'    => $this->config['weekLabels'][$nw],
                    ));

                    $date = $this->computeDate((int)substr($date, 0, 4), (int)substr($date, 5, 2), (int)substr($date, 8, 2), 1);
                }
            }
            $Tpl->add('week:loop');

            return true;
        }

        return false;
    }

    /**
     * 日付のループの組み立て
     *
     * @param Template & $Tpl
     * @return bool
     */
    protected function buildDays(&$Tpl)
    {
        $curW = $this->firstW;
        for ($day = 1; $day <= intval($this->loopCount); $day++) {
            $day_t = (($day + intval($this->firstDay) - 1) <= $this->lastDay)
                        ? $day + intval($this->firstDay) - 1
                        : (intval($this->firstDay) + $day) - $this->lastDay - 1;

            $date = $this->computeDate(
                (int)substr($this->startDate, 0, 4),
                (int)substr($this->startDate, 5, 2),
                (int)substr($this->startDate, 8, 2),
                ($this->firstW + (7 - $this->beginW)) % 7 + $day - 1
            );

            $calendar_vars = array(
                'week'  => $this->config['weekLabels'][$curW],
                'w'     => $curW,
                'day'   => $day_t,
                'date'  => $date,
            );
            if(date('Y-m-d', requestTime()) === $date){
                $calendar_vars += array(
                    'today' => $this->config['today']
                );
            }

            $this->buildEntries($Tpl, $date, 'entry:loop');

            if (!empty($this->config['weekLabels'][$curW])) {
                $Tpl->add('day:loop', $calendar_vars);
            }
            $curW = ($curW + 1) % 7;
            if ($this->config['mode'] === 'month') {
                if ($this->beginW == $curW) $Tpl->add('week:loop');
            }
        }
    }

    /**
     * エントリーのループの組み立て
     *
     * @param Template & $Tpl
     * @param string $date
     * @param string $block
     * @param bool|null $enableAround
     * @return bool
     */
    protected function buildEntries(&$Tpl, $date, $block, $enableAround = null)
    {
        $currentEntries = array();
        foreach ($this->entries as $entry) {
            if ($entry['entry_date'] === $date) {
                $currentEntries[] = array(
                    'eid'       => $entry['entry_id'],
                    'cid'       => $entry['entry_category_id'],
                    'bid'       => $entry['entry_blog_id'],
                    'title'     => addPrefixEntryTitle(
                        $entry['entry_title'],
                        $entry['entry_status'],
                        $entry['entry_start_datetime'],
                        $entry['entry_end_datetime'],
                        $entry['entry_approval']
                    ),
                    'link'      => $entry['entry_link'],
                    'status'    => $entry['entry_status'],
                    'date'      => $entry['entry_datetime'],
                );
                if (count($currentEntries) == $this->config['maxEntryCount']) {
                    break;
                }
            }
        }

        if (count($currentEntries) !== 0) {
            foreach ($currentEntries as $entry) {
                $link   = $entry['link'];
                $link   = !empty($link) ? $link : acmsLink(array(
                    'bid' => $entry['bid'],
                    'eid' => $entry['eid'],
                ));
                $vars = array(
                    'eid'       => $entry['eid'],
                    'title'     => $entry['title'],
                    'cid'       => $entry['cid'],
                    'bid'       => $entry['bid'],
                    'status'    => $entry['status'],
                );

                //------
                // date
                $vars += $this->buildDate($entry['date'], $Tpl, $block);

                if ($link != '#') {
                    $Tpl->add(array('url#rear', $block));
                    $vars['url']  = $link;
                }
                $vars += $this->buildField(loadEntryField($entry['eid']), $Tpl, $block);
                $curW = intval(date('w', strtotime($date)));
                if (!empty($this->config['weekLabels'][$curW]) && (is_null($enableAround) || $enableAround)) {
                    $Tpl->add($block, $vars);
                }
            }
        }
    }

    /**
     * dateブロックの変数の取得
     *
     * @return array
     */
    protected function getDateVars()
    {
        $week_title = array();

        switch($this->config['mode']){
            case "month" :
                $prevtime  = mktime(0, 0, 0, intval($this->m) - 1, 1, intval($this->y));
                $nexttime  = mktime(0, 0, 0, intval($this->m) + 1, 1, intval($this->y));
                list($py, $pm, $pd) = array(
                    date('Y', $prevtime),
                    date('m', $prevtime),
                    date('d', $prevtime)
                );
                list($ny, $nm, $nd) = array(
                    date('Y', $nexttime),
                    date('m', $nexttime),
                    date('d', $nexttime)
                );

                break;

            case "week" :
                $prev = $this->computeDate(intval($this->y), intval($this->m), intval($this->d), -7);
                $next = $this->computeDate(intval($this->y), intval($this->m), intval($this->d), 7);
                list($py, $pm, $pd) = explode('-', $prev);
                list($ny, $nm, $nd) = explode('-', $next);
                $week_title = array(
                    'firstWeekDay' => $this->firstDay
                );

                break;

            case "days" :
                $prev = $this->computeDate(intval($this->y), intval($this->m), intval($this->d), -$this->config['pagerCount']);
                $next = $this->computeDate(intval($this->y), intval($this->m), intval($this->d), $this->config['pagerCount']);
                list($py, $pm, $pd) = explode('-', $prev);
                list($ny, $nm, $nd) = explode('-', $next);
                $week_title = array(
                    'firstWeekDay' => $this->firstDay
                );

                break;
        }

        $vars = array(
            'year'      => $this->y,
            'month'     => $this->m,
            'day'       => substr($this->d, 0, 2),
            'prevDate'  => "$py/$pm/$pd",
            'nextDate'  => "$ny/$nm/$nd",
            'date'      => $this->ymd,
            'prevMonth' => $pm,
            'nextMonth' => $nm,
        );

        $vars += $week_title;

        return $vars;
    }
}
