<?php
/**
 * iCalcreator, the PHP class package managing iCal (rfc2445/rfc5445) calendar information.
 *
 * This file is a part of iCalcreator.
 *
 * @author    Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
 * @copyright 2007-2022 Kjell-Inge Gustafsson, kigkonsult, All rights reserved
 * @link      https://kigkonsult.se
 * @license   Subject matter of licence is the software iCalcreator.
 *            The above copyright, link, package and version notices,
 *            this licence notice and the invariant [rfc5545] PRODID result use
 *            as implemented and invoked in iCalcreator shall be included in
 *            all copies or substantial portions of the iCalcreator.
 *
 *            iCalcreator is free software: you can redistribute it and/or modify
 *            it under the terms of the GNU Lesser General Public License as
 *            published by the Free Software Foundation, either version 3 of
 *            the License, or (at your option) any later version.
 *
 *            iCalcreator is distributed in the hope that it will be useful,
 *            but WITHOUT ANY WARRANTY; without even the implied warranty of
 *            MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *            GNU Lesser General Public License for more details.
 *
 *            You should have received a copy of the GNU Lesser General Public License
 *            along with iCalcreator. If not, see <https://www.gnu.org/licenses/>.
 */
namespace Kigkonsult\Icalcreator\Util;

use DateTime;
use Exception;
use InvalidArgumentException;
use Kigkonsult\Icalcreator\DtBase;
use Kigkonsult\Icalcreator\IcalInterface;
use Kigkonsult\Icalcreator\Vcalendar;

/**
 * class VtimezonePopulateFactoryTest
 *
 * @since  2.27.14 - 2019-02-21
 */
class VtimezonePopulateFactoryTest extends DtBase
{
    /**
     * @var string
     */
    protected static string $ERRFMT = "%s Error in case #%s, %s, exp %s, got %s";

    /**
     * @var array|string[]
     */
    private static array $STCPAR = [ 'X-Y-Z' => 'VaLuE' ];

    /**
     * Testing VtimezonePopulateFactory::process, UTC (using Vcalendar::vtimezonePopulate())
     *
     * @test
     * @throws Exception
     * @throws InvalidArgumentException;
     */
    public function processTest1() : void
    {
        $calendar1 = new Vcalendar();

        $event     = $calendar1->newVevent( DATEYmdTHis );
        $vtimezone = $calendar1->newVtimezone(); // will below force removal in vtimezonePopulate

        $calendar2 = $calendar1->vtimezonePopulate();

        $vtimezone = $calendar2->getComponent( IcalInterface::VTIMEZONE );

        $expTz     = Vcalendar::UTC;
        $vtTzid    = $vtimezone->getTzid();
        $this->assertEquals(
            $expTz,
            $vtTzid,
            sprintf( self::$ERRFMT, __FUNCTION__, 11, IcalInterface::TZID, $expTz, $vtTzid )
        );
        $this->assertFalse( $vtimezone->getComponent( IcalInterface::STANDARD ));

        $this->parseCalendarTest( 12, $calendar1 );
    }

    /**
     * Testing VtimezonePopulateFactory::process, UTC
     *
     * @test
     * @throws Exception
     * @throws InvalidArgumentException;
     */
    public function processTest2() : void
    {
        $calendar1 = new Vcalendar();

        $event     = $calendar1->newVevent()->setDtstart(
            DATEYmdTHis,
            [ IcalInterface::TZID => OFFSET ]
        );
        $calendar2 = $calendar1->vtimezonePopulate();

        $vtimezone = $calendar2->getComponent( IcalInterface::VTIMEZONE );

        $expTz     = IcalInterface::UTC;
        $this->assertTrue(
            $vtimezone->isTzidSet(),
            sprintf( self::$ERRFMT, __FUNCTION__, 21, IcalInterface::TZID, IcalInterface::TRUE, IcalInterface::FALSE )
        );
        $vtTzid    = $vtimezone->getTzid();
        $this->assertEquals(
            $expTz,
            $vtTzid,
            sprintf( self::$ERRFMT, __FUNCTION__, 22, IcalInterface::TZID, $expTz, $vtTzid )
        );
        $this->assertFalse( $vtimezone->getComponent( IcalInterface::STANDARD ));

        $this->parseCalendarTest( 23, $calendar1 );
    }

    /**
     * processTest3 provider
     *
     * @return mixed[]
     * @throws Exception
     */
    public function processTest3Provider() : array
    {
        $dataArr = [];

        $timezone = 'Europe/Stockholm';

        $dataArr[] = [ // param timezone in X-prop X_WR_TIMEZONE and NO DTSTART
            1,
            $timezone,
            null,
            null, null,  // from/to
            []
        ];

        $dataArr[] = [ // param timezone in X-prop X_WR_TIMEZONE and ONE DTSTART
            2,
            $timezone,
            null,
            null, null,  // from/to
            [ '20170312' ]
        ];

        $dataArr[] = [ // param timezone in X-prop X_WR_TIMEZONE and TWO DTSTARTs
            3,
            $timezone,
            null,
            null, null,  // from/to
            [ '20160912', '20181113' ]
        ];

        $dataArr[] = [ // method arg timezone and ONE DTSTART
            3,
            null,
            $timezone,
            null, null,  // from/to
            [ '20170312' ]
        ];

        $dataArr[] = [ // method arg timezone and NO DTSTART
            4,
            null,
            $timezone,
            null, null,  // from/to
            [ '20170312' ]
        ];

        $dataArr[] = [ // method arg timezone and TWO DTSTARTs
            5,
            null,
            $timezone,
            null, null,  // from/to
            [ '20160912', '20181113' ]
        ];


        $from = DateTimeFactory::factory( '20161001', $timezone )->getTimestamp();
        $dataArr[] = [ // param timezone in X-prop X_WR_TIMEZONE and from
            6,
            $timezone,
            null,
            $from, null,  // from/to
            []
        ];

        $to = DateTimeFactory::factory( '20170312', $timezone )->getTimestamp();
        $dataArr[] = [ // method arg timezone and from
            7,
            $timezone,
            null,
            null, $to,  // from/to
            []
        ];

        $from = DateTimeFactory::factory( '20170312', $timezone )->modify( '-7 month' )->getTimestamp();
        $to   = DateTimeFactory::factory( '20170312', $timezone )->modify( '+18 month' )->getTimestamp();
        $dataArr[] = [ // param timezone in X-prop X_WR_TIMEZONE and from/to
            8,
            $timezone,
            null,
            $from, $to,  // from/to
            []
        ];


        $from = DateTimeFactory::factory( '20161001', $timezone )->getTimestamp();
        $dataArr[] = [ // param timezone in X-prop X_WR_TIMEZONE and from
            9,
            null,
            $timezone,
            $from, null,  // from/to
            []
        ];

//        $to = DateTimeFactory::factory( '20170312', $timezone )->getTimestamp();
        $to = DateTimeFactory::factory( '20170606', $timezone )->getTimestamp();
        $dataArr[] = [ // method arg timezone and from
            10,
            null,
            $timezone,
            null, $to,  // from/to
            []
        ];

        $from = DateTimeFactory::factory( '20170312', $timezone )->modify( '-7 month' )->getTimestamp();
        $to   = DateTimeFactory::factory( '20170312', $timezone )->modify( '+18 month' )->getTimestamp();
        $dataArr[] = [ // param timezone in X-prop X_WR_TIMEZONE and from/to
            11,
            null,
            $timezone,
            $from, $to,  // from/to
            []
        ];

        return $dataArr;
    }

    /**
     * Testing VtimezonePopulateFactory::process, new TZUNTIL (and include TZID_ALIAS_OF)
     *
     * @test
     * @dataProvider processTest3Provider
     * @param int $case
     * @param mixed $xParamTz
     * @param mixed $mParamTz
     * @param int|null $from
     * @param int|null $to
     * @param mixed[] $dtstarts
     * @throws Exception
     */
    public function processTest3( int $case, mixed $xParamTz, mixed $mParamTz, null|int $from, null|int $to, array $dtstarts ) : void
    {
        $calendar1 = new Vcalendar();

        if( ! empty( $xParamTz )) {
            $calendar1->setXprop( IcalInterface::X_WR_TIMEZONE, $xParamTz );
        }
        $params = ['X-case' => $case ] + self::$STCPAR;
        foreach( $params as $k => $v ) {
            $calendar1->setXprop( $k, $v );
        }

        foreach( $dtstarts as $dtstartValue ) {
//          $e = $calendar1->newVevent( $dtstartValue );
            $vav = $calendar1->newVavailability( $dtstartValue );
        }

        $c2 = VtimezonePopulateFactory::process( // with TZUNTIL set!!
            $calendar1,
            $mParamTz ?: null,
            $params,
            $from ?: null,
            $to ?: null
        );

        $vtimezone = $c2->getComponent( IcalInterface::VTIMEZONE );

        $expTz  = ( ! empty( $mParamTz )) ? $mParamTz : $xParamTz;
        $vtTzid = $vtimezone->getTzid();
        $this->assertEquals(
            $expTz,
            $vtTzid,
            sprintf( self::$ERRFMT, __FUNCTION__, $case . '-1', IcalInterface::TZID, $expTz, $vtTzid )
        );

        // test get/set of TZID-ALIAS-OF in Vtimezone (with TZID value)
        $this->assertFalse(
            $vtimezone->isTzidaliasofSet(),
                sprintf( self::$ERRFMT, __FUNCTION__, $case . '-2a', IcalInterface::TZID_ALIAS_OF, IcalInterface::FALSE, IcalInterface::TRUE )
        );
        $vtimezone->setTzidaliasof( $vtTzid . 1 );
        $this->assertTrue(
            $vtimezone->isTzidaliasofSet(),
            sprintf( self::$ERRFMT, __FUNCTION__, $case . '-2b', IcalInterface::TZID_ALIAS_OF, IcalInterface::TRUE, IcalInterface::FALSE )
        );
        $vtimezone->setTzidaliasof( $vtTzid . 2 );
        foreach( [ 1, 2 ] as $x ) {
            $this->assertEquals(
                $vtTzid . $x,
                $vtimezone->getTzidaliasof(),
                sprintf( self::$ERRFMT, __FUNCTION__, $case . '-2c-' . $x, IcalInterface::TZID_ALIAS_OF, $expTz, $vtTzid )
            );
        }

        $calendar1->replaceComponent( $vtimezone ); // assure TZID_ALIAS_OF is set in calendars Vtimezone

        // test isset TZUNTIL
        $this->assertTrue(
            $vtimezone->isTzuntilSet(),
            sprintf( self::$ERRFMT, __FUNCTION__, $case . '-3a', IcalInterface::TZUNTIL, IcalInterface::TRUE, IcalInterface::FALSE )
        );
        $tzUntil = $vtimezone->getTzuntil();
        $this->assertInstanceOf(
            DateTime::class,
            $tzUntil,
            sprintf( self::$ERRFMT, __FUNCTION__, $case . '-3c', IcalInterface::TZUNTIL, $expTz, $vtTzid )
        );

        $standard = $vtimezone->getComponent( IcalInterface::STANDARD );
        $this->assertNotFalse(
            $standard,
            sprintf(
                self::$ERRFMT,
                __FUNCTION__,
                $case . '-4',
                IcalInterface::STANDARD,
                IcalInterface::STANDARD,
                'false'
            )
        );

        $this->assertTrue(
            $standard->isTzoffsetfromSet(),
            sprintf( self::$ERRFMT, __FUNCTION__, $case . '-5a', IcalInterface::TZOFFSETFROM, IcalInterface::TRUE, IcalInterface::FALSE )
        );
        $getValue = $standard->getTzoffsetfrom();
        $this->assertEquals(
            '+0200',
            $getValue,
            sprintf(
                self::$ERRFMT,
                __FUNCTION__,
                $case . '-5c',
                IcalInterface::STANDARD . '::' . IcalInterface::TZOFFSETFROM,
                '+0200',
                $getValue
            )
        );

        $this->assertTrue(
            $standard->isTzoffsetfromSet(),
            sprintf( self::$ERRFMT, __FUNCTION__, $case . '-6a', IcalInterface::TZOFFSETTO, IcalInterface::TRUE, IcalInterface::FALSE )
        );
        $getValue = $standard->getTzoffsetTo();
        $this->assertEquals(
            '+0100',
            $getValue,
            sprintf(
                self::$ERRFMT,
                __FUNCTION__,
                $case . '-6c',
                IcalInterface::STANDARD . '::' . IcalInterface::TZOFFSETTO,
                '+0100',
                $getValue
            )
        );

        $getValue = $standard->getRdate( 1 );
        $this->assertTrue(
             ( false === $getValue ) ||
            (( 10 === (int) $getValue[0]->format( 'm' )) &&
             (  3 === (int) $getValue[0]->format( 'H' )) &&
             (  0 === (int) $getValue[0]->format( 'i' ) )),
            sprintf(
                self::$ERRFMT,
                __FUNCTION__,
                $case . '-7',
                IcalInterface::STANDARD . '::' . IcalInterface::RDATE,
                '20xx-10-xx-03-00-00',
                var_export( $getValue, true )
            )
        );

        $daylight = $vtimezone->getComponent( IcalInterface::DAYLIGHT );
        $this->assertNotFalse(
            $daylight,
            sprintf(
                self::$ERRFMT,
                __FUNCTION__,
                $case . '-8',
                IcalInterface::DAYLIGHT,
                IcalInterface::DAYLIGHT,
                'false'
            )
        );

        $getValue = $daylight->getTzoffsetfrom();
        $this->assertEquals(
            '+0100',
            $getValue,
            sprintf(
                self::$ERRFMT,
                __FUNCTION__,
                $case . '-9',
                IcalInterface::DAYLIGHT . '::' . IcalInterface::TZOFFSETFROM,
                '+0100',
                $getValue
            )
        );

        $getValue = $daylight->getTzoffsetTo();
        $this->assertEquals(
            '+0200',
            $getValue,
            sprintf(
                self::$ERRFMT,
                __FUNCTION__,
                $case . '-10',
                IcalInterface::DAYLIGHT . '::' . IcalInterface::TZOFFSETTO,
                '+0200',
                $getValue
            )
        );

        $getValue = $daylight->getRdate( 1 );
        $this->assertTrue(
            ( ( false === $getValue ) ||
            (( 3 === (int) $getValue[0]->format( 'm' )) &&
             ( 2 === (int) $getValue[0]->format( 'H' )) &&
             ( 0 === (int) $getValue[0]->format( 'i' ) ))),
            sprintf(
                self::$ERRFMT,
                __FUNCTION__,
                $case . '-11',
                IcalInterface::DAYLIGHT . '::' . IcalInterface::RDATE,
                '20xx-03-xx-02-00-00',
                var_export( $getValue, true )
            )
        );

        $this->parseCalendarTest( $case, $calendar1, IcalInterface::TZUNTIL ); // force xml+parse
        $calendar1Str = $calendar1->createCalendar();

        // fetch all components
        $vtimezone->resetCompCounter();  // REQUIRED
        $compArr = [];
        while( $comp = $vtimezone->getComponent()) {
            $compArr[] = $comp;
        }

        $x = 1;
        while( $vtimezone->deleteComponent( $x )) {
            ++$x;
        }
        $this->assertSame(
            0,
            $vtimezone->countComponents(),
            'deleteComponent-error ' . $case . '-12, has ' . $vtimezone->countComponents()
        );
        // set components again
        foreach( $compArr as $comp ) {
            $vtimezone->setComponent( $comp );
        }
        // check number of components
        $this->assertSame(
            count( $compArr ),
            $vtimezone->countComponents(),
            'setComponent-error ' . $case . '-13, has ' . $vtimezone->countComponents()
        );

        $vtimezone2 = $calendar1->getComponent( IcalInterface::VTIMEZONE, 1 );
        // check number of components
        $this->assertSame(
            count( $compArr ),
            $vtimezone2->countComponents(),
            'setComponent-error ' . $case . '-14, has ' . $vtimezone2->countComponents()
        );

        $calendar1->replaceComponent( $vtimezone2 );

        $this->assertEquals(
            $calendar1Str,
            $calendar1->createCalendar(),
            'calendar compare error ' . $case . '-15'
        );
    }

    /**
     * Testing VtimezonePopulateFactory::process, multiple timezones as timezone arg, test twice
     *
     * @test
     * @throws Exception
     */
    public function processTest4() : void
    {
        $timezone1 = 'Europe/Stockholm';
        $timezone2 = 'America/New_York';
        $timezone3 = 'Europe/Moscow';
        $timezone4 = 'America/Los_Angeles';

        $vCalendar = new Vcalendar();

        // set two timezones into calendar 
        $vCalendar = VtimezonePopulateFactory::process(
            $vCalendar,
            [ $timezone1, $timezone2 ]
        );

        // check first 
        $this->assertNotFalse(
            $vTimezone = $vCalendar->getComponent( Vcalendar::VTIMEZONE, 1 ),
            __METHOD__ . ' 11 Vtimezone not found'
        );
        $this->assertNotFalse(
            $tzId = $vTimezone->getTzId(),
            __METHOD__ . ' 12 TZID not found'
        );
        $this->assertSame(
            $timezone1,
            $tzId,
            __METHOD__ . ' 13 expected ' . $timezone1 . ' got ' . $tzId
        );

        // check second 
        $this->assertNotFalse(
            $vTimezone = $vCalendar->getComponent( Vcalendar::VTIMEZONE, 2 ),
            __METHOD__ . ' 21 Vtimezone not found'
        );
        $this->assertNotFalse(
            $tzId = $vTimezone->getTzId(),
            __METHOD__ . ' 22 TZID not found'
        );
        $this->assertSame(
            $timezone2,
            $tzId,
            __METHOD__ . ' 23 expected ' . $timezone1 . ' got ' . $tzId
        );

        // set two other timezones into calendar
        $vCalendar = VtimezonePopulateFactory::process(
            $vCalendar,
            [ $timezone3, $timezone4 ]
        );

        // check first 
        $this->assertNotFalse(
            $vTimezone = $vCalendar->getComponent( Vcalendar::VTIMEZONE, 1 ),
            __METHOD__ . ' 31 Vtimezone not found'
        );
        $this->assertNotFalse(
            $tzId = $vTimezone->getTzId(),
            __METHOD__ . ' 32 TZID not found'
        );
        $this->assertSame(
            $timezone3,
            $tzId,
            __METHOD__ . ' 33 expected ' . $timezone3 . ' got ' . $tzId
        );

        // check second 
        $this->assertNotFalse(
            $vTimezone = $vCalendar->getComponent( Vcalendar::VTIMEZONE, 2 ),
            __METHOD__ . ' 41 Vtimezone not found'
        );
        $this->assertNotFalse(
            $tzId = $vTimezone->getTzId(),
            __METHOD__ . ' 42 TZID not found'
        );
        $this->assertSame(
            $timezone4,
            $tzId,
            __METHOD__ . ' 43 expected ' . $timezone4 . ' got ' . $tzId
        );
    }
}