#-------------------------------------------------------------------
# WebGUI is Copyright 2001-2009 Plain Black Corporation.
#-------------------------------------------------------------------
# Please read the legal notices (docs/legal.txt) and the license
# (docs/license.txt) that came with this distribution before using
# this software.
#-------------------------------------------------------------------
# http://www.plainblack.com                     info@plainblack.com
#-------------------------------------------------------------------

use FindBin;
use strict;
use lib "$FindBin::Bin/../../lib";

##The goal of this test is to test the creation of Calendar Wobjects.

my @icalWrapTests = (
    {
        in      => 'Text is passed through with no problems',
        out     => 'Text is passed through with no problems',
        comment => 'Text passed through with no problems',
    },
    {
        in      => ',Escape more than one, multiple, commas,',
        out     => '\,Escape more than one\, multiple\, commas\,',
        comment => 'escape commas',
    },
    {
        in      => ';Escape more than one; multiple; semicolons;',
        out     => '\;Escape more than one\; multiple\; semicolons\;',
        comment => 'escape semicolons',
    },
    {
        in      => '\\Escape more than one\\ multiple\\ backslashes\\',
        out     => '\\\\Escape more than one\\\\ multiple\\\\ backslashes\\\\',
        comment => 'escape backslashes',
    },
    {
        in      => "lots\nand\nlots\nof\nnewlines\n",
        out     => 'lots\\nand\\nlots\\nof\\nnewlines\\n',
        comment => 'escape newlines',
    },
    {
                   #         1         2         3         4         5         6         7   V
                   #12345678901234567890123456789012345678901234567890123456789012345678901234567890
        in      => "There's not a day goes by I don't feel regret. Not because I'm in here, or because you think I should. I look back on the way I was then: a young, stupid kid who committed that terrible crime. I want to talk to him.",
        out     => "There's not a day goes by I don't feel regret. Not because I'm in here\\,\r\n or because you think I should. I look back on the way I was then: a\r\n young\\, stupid kid who committed that terrible crime. I want to talk to\r\n him.",
        comment => 'basic wrapping',
    },
);

use WebGUI::Test;
use WebGUI::Session;
use Test::More;
use Test::Deep;
use Data::Dumper;
use WebGUI::Asset::Wobject::Calendar;
use WebGUI::Asset::Event;

my $session = WebGUI::Test->session;

# Do our work in the import node
my $node = WebGUI::Asset->getImportNode($session);

my $versionTag = WebGUI::VersionTag->getWorking($session);
$versionTag->set({name=>"Calendar Test"});
addToCleanup($versionTag);

my $cal = $node->addChild({className=>'WebGUI::Asset::Wobject::Calendar'});
my $windowCal = $node->addChild({
    className => 'WebGUI::Asset::Wobject::Calendar',
    title     => 'Calendar for doing event window testing',
});
$versionTag->commit();

# Test for a sane object type
isa_ok($cal, 'WebGUI::Asset::Wobject::Calendar');

# Test addChild to make sure we can only add Event assets as children to the calendar
my $event = $cal->addChild({className=>'WebGUI::Asset::Event'});
isa_ok($event, 'WebGUI::Asset::Event','Can add Events as a child to the calendar.');

my $dt = WebGUI::DateTime->new($session, mysql => '2001-08-16 8:00:00', time_zone => 'America/Chicago');

my $vars = {};
$cal->appendTemplateVarsDateTime($vars, $dt, "start");
cmp_deeply(
    $vars,
    {
        startMinute     => '00',
        startDayOfMonth => 16,
        startMonthName  => 'August',
        startMonthAbbr  => 'Aug',
        startEpoch      => 997966800,
        startHms        => '08:00:00',
        startM          => 'AM',
        startMeridiem   => 'AM',
        startDayName    => 'Thursday',
        startMdy        => '08-16-2001',
        startYmd        => '2001-08-16',
        startDmy        => '16-08-2001',
        startDayAbbr    => 'Thu',
        startDayOfWeek  => 4,
        startHour       => 8,
        startHour24     => 8,
        startMonth      => 8,
        startSecond     => '00',
        startYear       => 2001,
    },
    'Variables returned by appendTemplateVarsDateTime'
);

######################################################################
#
# getEventsIn
#
######################################################################

my $tz   = $session->datetime->getTimeZone();
my $bday = WebGUI::DateTime->new($session, WebGUI::Test->webguiBirthday);

##Simulate how windows are built in each view method
my $startDt     = $bday->cloneToUserTimeZone->truncate(to => 'day')->subtract(days => 1);
my $windowStart = $startDt->clone;
my $endDt       = $startDt->clone->add(days => 2);
my $windowEnd   = $endDt->clone->subtract(seconds => 1);

my $inside = $windowCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Inside window, no times, same day',
    startDate   => $bday->toDatabaseDate,
    endDate     => $bday->toDatabaseDate,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $insidewt = $windowCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Inside window, with times',
    startDate   => $bday->toDatabaseDate,
    endDate     => $bday->toDatabaseDate,
    startTime   => $bday->toDatabaseTime,
    endTime     => $bday->clone->add(hours => 1)->toDatabaseTime,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $outsideHigh = $windowCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Outside window, after time',
    startDate   => $endDt->clone->add(days => 2)->toDatabaseDate,
    endDate     => $endDt->clone->add(days => 3)->toDatabaseDate,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $outsideLow = $windowCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Outside window, before time',
    startDate   => $startDt->clone->subtract(days => 3)->toDatabaseDate,
    endDate     => $startDt->clone->subtract(days => 2)->toDatabaseDate,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $straddle = $windowCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Straddles the window, inclusive',
    startDate   => $startDt->clone->subtract(days => 1)->toDatabaseDate,
    endDate     => $endDt->clone->add(days => 1)->toDatabaseDate,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $straddlewt = $windowCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Straddles the window with times, inclusive',
    startDate   => $startDt->clone->subtract(hours => 12)->toDatabaseDate,
    endDate     => $endDt->clone->add(hours => 12)->toDatabaseDate,
    startTime   => $startDt->clone->subtract(hours => 12)->toDatabaseTime,
    endTime     => $endDt->clone->add(hours => 12)->toDatabaseTime,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $straddleLowwt = $windowCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Straddles the window, lower side',
    startDate   => $startDt->clone->subtract(hours => 12)->toDatabaseDate,
    endDate     => $startDt->clone->add(hours => 12)->toDatabaseDate,
    startTime   => $startDt->clone->subtract(hours => 12)->toDatabaseTime,
    endTime     => $startDt->clone->add(hours => 12)->toDatabaseTime,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $straddleHighwt = $windowCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Straddles the window, higher side',
    startDate   => $endDt->clone->subtract(hours => 12)->toDatabaseDate,
    endDate     => $endDt->clone->add(hours => 12)->toDatabaseDate,
    startTime   => $endDt->clone->subtract(hours => 12)->toDatabaseTime,
    endTime     => $endDt->clone->add(hours => 12)->toDatabaseTime,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $justBeforewt = $windowCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Just before the window.  Ending time coincident with window start',
    startDate   => $startDt->clone->subtract(hours => 1)->toDatabaseDate,
    endDate     => $startDt->toDatabaseDate,
    startTime   => $startDt->clone->subtract(hours => 1)->toDatabaseTime,
    endTime     => $startDt->toDatabaseTime,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $justAfterwt = $windowCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Just after the window.  Start time coincident with window end',
    startDate   => $endDt->toDatabaseDate,
    endDate     => $endDt->clone->add(hours => 1)->toDatabaseDate,
    startTime   => $endDt->toDatabaseTime,
    endTime     => $endDt->clone->add(hours => 1)->toDatabaseTime,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $justBefore = $windowCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Just before the window.  Ending date coincident with window start',
    startDate   => $startDt->clone->add(days => -1)->toDatabaseDate,
    endDate     => $startDt->clone->add(days => -1)->toDatabaseDate,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $justAfter = $windowCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Just after the window.  Start date coincident with window end',
    startDate   => $endDt->clone->add(days => 1)->toDatabaseDate,
    endDate     => $endDt->clone->add(days => 1)->toDatabaseDate,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $starting = $windowCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Inside the window, same start date',
    startDate   => $startDt->toDatabaseDate,
    endDate     => $startDt->toDatabaseDate,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $ending = $windowCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Inside the window, same end date',
    startDate   => $endDt->clone->add(days => -1)->toDatabaseDate,
    endDate     => $endDt->clone->add(days => -1)->toDatabaseDate,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $coincident = $windowCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Coincident with the window start and window end',
    startDate   => $startDt->toDatabaseDate,
    endDate     => $endDt->toDatabaseDate,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $coincidentLow = $windowCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Coincident with the window start',
    startDate   => $startDt->toDatabaseDate,
    endDate     => $endDt->clone->add(days => 1)->toDatabaseDate,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $coincidentHigh = $windowCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Coincident with the window end',
    startDate   => $startDt->clone->add( days => -1, )->toDatabaseDate,
    endDate     => $endDt->toDatabaseDate,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

#    no suffix = all day event
#    wt suffix = with times
#                      inside
#                      insidewt
#          |-------------straddle-----------------|
#          |-------------straddlewt---------------|
#      straddleLowwt
#                                           straddleHighwt
#              |----------coincident-----------|
#              |----------coincidentLow------------------|
#    |--------------------coincidentHigh-------|
# window:      |-------------------------------|
#  starting--->|
#                                             |<---ending
#   justBeforewt                               justAfterwt
#     justBefore                               justAfter
#                                                 outside high
# outside low
#
# Everything above the window should be included in the set of events returned.

my $tag2 = WebGUI::VersionTag->getWorking($session);
$tag2->commit;
addToCleanup($tag2);

is(scalar @{ $windowCal->getLineage(['children'])}, 17, 'added events to the window calendar');

my @window = $windowCal->getEventsIn($windowStart->toDatabase, $windowEnd->toDatabase);

cmp_bag(
    [ map { $_->get('title') } @window ],
    [ map { $_->get('title') }
        ($inside,     $insidewt,
         $straddle,   $straddleHighwt, $straddleLowwt,  $straddlewt,
         $coincident, $coincidentLow,  $coincidentHigh, $starting,
         $ending, )
    ],
    '..returns correct set of events'
);

######################################################################
#
# viewWeek
#
######################################################################

my $weekCal = $node->addChild({
    className => 'WebGUI::Asset::Wobject::Calendar',
    title     => 'Calendar for doing event span testing, week',
});

my $allDayDt = $bday->cloneToUserTimeZone;

my $nextWeekDt = $bday->cloneToUserTimeZone->add(weeks => 1)->truncate( to => 'week')->add(days => 6, hours => 19);

my $allDay = $weekCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'An event with explicit times that lasts all day',
    startDate   => $allDayDt->toDatabaseDate,
    endDate     => $allDayDt->clone->add(days => 1)->toDatabaseDate,
    startTime   => $allDayDt->clone->truncate(to => 'day')->toDatabaseTime,
    endTime     => $allDayDt->clone->add(days => 1)->truncate(to => 'day')->toDatabaseTime,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $endOfWeek = $weekCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Event at the end of the week',
    startDate   => $nextWeekDt->toDatabaseDate,
    endDate     => $nextWeekDt->toDatabaseDate,
    startTime   => $nextWeekDt->toDatabaseTime,
    endTime     => $nextWeekDt->clone->add(hours => 1)->toDatabaseTime,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $tag3 = WebGUI::VersionTag->getWorking($session);
$tag3->commit;
addToCleanup($tag3);

my $weekVars = $weekCal->viewWeek({ start => $bday });
my @eventBins = ();
foreach my $day (@{ $weekVars->{days} }) {
    if (exists $day->{events} and scalar @{ $day->{events} } > 0) {
        push @eventBins, $day->{dayOfWeek};
    }
}

cmp_deeply(
    \@eventBins,
    [ 4 ],
    'viewWeek: all day event is only in 1 day when time zones line up correctly'
);

$weekVars = $weekCal->viewWeek({ start => $nextWeekDt });
@eventBins = ();
foreach my $day (@{ $weekVars->{days} }) {
    if (exists $day->{events} and scalar @{ $day->{events} } > 0) {
        push @eventBins, $day->{dayOfWeek};
    }
}

cmp_deeply(
    \@eventBins,
    [ 7 ],
    '... end of week event in proper bin, considering time zone'
);

################################################################
#
# wrapIcal
#
################################################################

#Any old calendar will do for these tests.

foreach my $test (@icalWrapTests) {
    my ($in, $out, $comment) = @{ $test }{ qw/in out comment/ };
    my $wrapOut = $cal->wrapIcal($in);
    is ($wrapOut, $out, $comment);
}

######################################################################
#
# viewMonth
#
######################################################################

my $monthCal = $node->addChild({
    className => 'WebGUI::Asset::Wobject::Calendar',
    title     => 'Calendar for doing event span testing, month',
});

$allDayDt       = $bday->cloneToUserTimeZone;
my $nextMonthDt = $bday->cloneToUserTimeZone->add(months => 1)->truncate( to => 'month')->add(days => 29, hours => 19);

$allDay = $monthCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'An event with explicit times that lasts all day',
    startDate   => $allDayDt->toDatabaseDate,
    endDate     => $allDayDt->clone->add(days => 1)->toDatabaseDate,
    startTime   => $allDayDt->clone->truncate(to => 'day')->toDatabaseTime,
    endTime     => $allDayDt->clone->add(days => 1)->truncate(to => 'day')->toDatabaseTime,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $endOfMonth = $monthCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Event at the end of the month',
    startDate   => $nextMonthDt->toDatabaseDate,
    endDate     => $nextMonthDt->toDatabaseDate,
    startTime   => $nextMonthDt->toDatabaseTime,
    endTime     => $nextMonthDt->clone->add(hours => 1)->toDatabaseTime,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $tag4 = WebGUI::VersionTag->getWorking($session);
$tag4->commit;
addToCleanup($tag4);

my $monthVars = $monthCal->viewMonth({ start => $bday });
@eventBins = ();
foreach my $week ( @{ $monthVars->{weeks} } ) {
    foreach my $day (@{ $week->{days} }) {
        if (exists $day->{events} and scalar @{ $day->{events} } > 0) {
            push @eventBins, $day->{dayMonth};
        }
    }
}

cmp_deeply(
    \@eventBins,
    [ 16 ],
    'viewMonth: all day event is only in 1 day when time zones line up correctly'
);

$monthVars = $monthCal->viewMonth({ start => $nextMonthDt });
@eventBins = ();
foreach my $week ( @{ $monthVars->{weeks} } ) {
    foreach my $day (@{ $week->{days} }) {
        if (exists $day->{events} and scalar @{ $day->{events} } > 0) {
            push @eventBins, $day->{dayMonth};
        }
    }
}

cmp_deeply(
    \@eventBins,
    [ 30 ],
    '... end of month event in proper bin'
);


######################################################################
#
# viewDay
#
######################################################################

my $dayCal = $node->addChild({
    className => 'WebGUI::Asset::Wobject::Calendar',
    title     => 'Calendar for doing event span testing, day',
});

$allDayDt     = $bday->cloneToUserTimeZone;
my $nextDayDt = $bday->cloneToUserTimeZone->add(days => 1)->truncate( to => 'day')->add(hours => 19);

$allDay = $dayCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'An event with explicit times that lasts all day',
    startDate   => $allDayDt->toDatabaseDate,
    endDate     => $allDayDt->clone->add(days => 1)->toDatabaseDate,
    startTime   => $allDayDt->clone->truncate(to => 'day')->toDatabaseTime,
    endTime     => $allDayDt->clone->add(days => 1)->truncate(to => 'day')->toDatabaseTime,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $nextDay = $dayCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Event at the end of the next day',
    startDate   => $nextDayDt->toDatabaseDate,
    endDate     => $nextDayDt->toDatabaseDate,
    startTime   => $nextDayDt->toDatabaseTime,
    endTime     => $nextDayDt->clone->add(hours => 1)->toDatabaseTime,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $tag5 = WebGUI::VersionTag->getWorking($session);
$tag5->commit;
addToCleanup($tag5);

my $hourVars = $dayCal->viewDay({ start => $nextDayDt });
@eventBins = ();
foreach my $slot (@{ $hourVars->{hours} }) {
    if (exists $slot->{events} and scalar @{ $slot->{events} } > 0) {
        push @eventBins, $slot->{hour24};
    }
}

cmp_deeply(
    \@eventBins,
    [ 19 ],
    '... end of day event in proper bin'
);

######################################################################
#
# viewList
#
######################################################################

my $listCal = $node->addChild({
    className            => 'WebGUI::Asset::Wobject::Calendar',
    title                => 'Calendar for doing event span testing, list',
    listViewPageInterval => 3600*24*3,
});

$allDayDt     = $bday->cloneToUserTimeZone->truncate( to => 'day' );
my $prevDayDt = $bday->cloneToUserTimeZone->truncate( to => 'day' )->subtract(days => 1)->add(hours => 19);

$allDay = $listCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'An event with explicit times that lasts all day',
    startDate   => $allDayDt->toDatabaseDate,
    endDate     => $allDayDt->clone->add(days => 1)->toDatabaseDate,
    startTime   => $allDayDt->toDatabaseTime,
    endTime     => $allDayDt->clone->add(days => 1)->toDatabaseTime,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $prevDay = $listCal->addChild({
    className   => 'WebGUI::Asset::Event',
    title       => 'Event at the end of the previous day',
    startDate   => $prevDayDt->toDatabaseDate,
    endDate     => $prevDayDt->toDatabaseDate,
    startTime   => $prevDayDt->toDatabaseTime,
    endTime     => $prevDayDt->clone->add(hours => 1)->toDatabaseTime,
    timeZone    => $tz,
}, undef, undef, {skipAutoCommitWorkflows => 1});

my $tag6 = WebGUI::VersionTag->getWorking($session);
$tag6->commit;
addToCleanup($tag6);

my $listVars = $listCal->viewList({ start => $bday });

@eventBins = ();
foreach my $event (@{ $listVars->{events} }) {
    push @eventBins, $event->{eventAssetId};
}

cmp_deeply(
    \@eventBins,
    [ $allDay->getId ],
    '... correct set of events in list view'
);

ok(exists $listVars->{events}->[0]->{new_year} && $listVars->{events}->[0]->{new_year}, 'first event has new_year set');
ok(exists $listVars->{events}->[0]->{new_month} && $listVars->{events}->[0]->{new_month}, 'first event has new_month set');
ok(exists $listVars->{events}->[0]->{new_day} && $listVars->{events}->[0]->{new_day}, 'first event has new_day set');

######################################################################
#
# getFeeds
#
######################################################################

my $feedCal = $node->addChild({
    className => 'WebGUI::Asset::Wobject::Calendar',
    title     => 'Calendar for doing feed tests',
});

my $feedTag = WebGUI::VersionTag->getWorking($session);
$feedTag->set({name=>"Calendar Feed Test"});
addToCleanup($feedTag);
$feedTag->commit;

cmp_deeply(
    $feedCal->getFeeds(),
    [],
    'getFeeds: returns an empty array ref with no feeds'
);

##Update with JSON and try again :)
$feedCal->update({icalFeeds => '[]'});
is_deeply $feedCal->get('icalFeeds'), [], 'set as JSON, returned perl';

$feedCal->{_properties}->{icalFeeds} = '[]';
is $feedCal->get('icalFeeds'), '[]', 'poked into the object directly, returned as JSON';

cmp_deeply(
    $feedCal->getFeeds(),
    [],
    'but getFeeds still returns a data structure.'
);

done_testing;
