# vim:syntax=perl
#-------------------------------------------------------------------
# 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
#------------------------------------------------------------------

# Write a little about what this script tests.
# 
#

use FindBin;
use strict;
use lib "$FindBin::Bin/../../lib";
use Test::More;
use Test::Deep;
use File::Copy qw/mv/;
use File::Path;
use Data::Dumper;
use Path::Class;

use WebGUI::Test; # Must use this before any other WebGUI modules
use WebGUI::Test::Maker::Permission;
use WebGUI::Session;
use WebGUI::Text;
use WebGUI::Utility;
use WebGUI::DateTime;
use DateTime;

################################################################
#
#  setup session, users and groups for this test
#
################################################################

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

my $staff = WebGUI::Group->new($session, 'new');
WebGUI::Test->addToCleanup($staff);
$staff->name('Reporting Staff');

my $reporter = WebGUI::User->new($session, 'new');
$reporter->username('reporter');
my $editor   = WebGUI::User->new($session, 'new');
$editor->username('editor');
my $reader   = WebGUI::User->new($session, 'new');
$reader->username('reader');
$staff->addUsers([$reporter->userId]);
WebGUI::Test->addToCleanup($reporter, $editor, $reader);

my $archive = 'placeholder for Test::Maker::Permission';

my $canPostMaker = WebGUI::Test::Maker::Permission->new();
$canPostMaker->prepare({
    object   => $archive,
    session  => $session,
    method   => 'canPostStories',
    pass     => [3, $editor, $reporter ],
    fail     => [1, $reader            ],
});

my $tests = 56
          + $canPostMaker->plan
          ;
plan tests => 1
            + $tests;

#----------------------------------------------------------------------------
# put your tests here

my $class  = 'WebGUI::Asset::Wobject::StoryArchive';
my $loaded = use_ok($class);

my $storage;
my $versionTag;

my $creationDateSth = $session->db->prepare('update asset set creationDate=? where assetId=?');
my @skipAutoCommit  = (undef, undef, { skipAutoCommitWorkflows => 1 });

SKIP: {

skip "Unable to load module $class", $tests unless $loaded;
my $home = WebGUI::Asset->getDefault($session);

$archive    = $home->addChild({
                className => $class,
                title => 'My Stories',
                url => '/home/mystories',
                styleTemplateId => $home->get('styleTemplateId'),
              });
$versionTag = WebGUI::VersionTag->getWorking($session);
$versionTag->commit;
WebGUI::Test->addToCleanup($versionTag);
$archive = $archive->cloneFromDb;

isa_ok($archive, 'WebGUI::Asset::Wobject::StoryArchive', 'created StoryArchive');

################################################################
#
#  canPostStories
#
################################################################

$archive->update({
    ownerUserId => $editor->userId,
    groupToPost => $staff->getId,
});

is($archive->get('groupToPost'), $staff->getId, 'set Staff group to post to Story Archive');

$canPostMaker->{_tests}->[0]->{object} = $archive;

$canPostMaker->run();

################################################################
#
#  getFolder
#
################################################################

##Note, this is just to prevent date rollover from happening.
##We'll test implicit getFolder later on.
my $now = time();
my $todayFolder = $archive->getFolder($now);
isa_ok($todayFolder, 'WebGUI::Asset::Wobject::Folder', 'getFolder created a Folder');
is($archive->getChildCount, 1, '... created a child');
my $dt = DateTime->from_epoch(epoch => $now, time_zone => $session->datetime->getTimeZone);
my $folderName = $dt->strftime('%B_%d_%Y');
$folderName =~ s/^(\w+_)0/$1/;
is($todayFolder->getTitle, $folderName, '... folder has the right name');
my $folderUrl = $archive->getFolderUrl($folderName);
is($todayFolder->get('url'), $folderUrl, '... folder has the right URL');
is($todayFolder->getParent->getId, $archive->getId, '... created folder has the right parent');
is($todayFolder->get('state'),  'published', '... created folder is published');
is($todayFolder->get('status'), 'approved',  '... created folder is approved');
is($todayFolder->get('styleTemplateId'), $archive->get('styleTemplateId'),  '... created folder has correct styleTemplateId');

{
    my $undo = WebGUI::Test->overrideSetting(urlExtension => 'ext');
    my $arch2 = $home->addChild({
        className => $class,
        title     => 'Extension Tester',
    });
    addToCleanup($arch2);

    is $arch2->get('url'),
        'home/extension-tester.ext',
        'ext added';

    is $arch2->getFolderUrl('blah'),
        'home/extension-tester/blah.ext',
        'folder url: strip extension from parent and add to child';

    my $folder = $arch2->getFolder($now);
    ok defined $folder, 'getFolder with url extension';

    is $folder->get('url'),
        $arch2->getFolderUrl($folder->getMenuTitle),
        'getFolderUrl and folder getUrl match';
}

my $sameFolder = $archive->getFolder($now);
is($sameFolder->getId, $todayFolder->getId, 'call with same time returns the same folder');
undef $sameFolder;

my ($startOfDay, $endOfDay) = $session->datetime->dayStartEnd($now);
$sameFolder = $archive->getFolder($startOfDay);
is($sameFolder->getId, $todayFolder->getId, 'call within same day(start) returns the same folder');
undef $sameFolder;
$sameFolder = $archive->getFolder($endOfDay);
is($sameFolder->getId, $todayFolder->getId, 'call within same day(end) returns the same folder');
undef $sameFolder;
$todayFolder->purge;
is($archive->getChildCount, 0, 'leaving with an empty archive');

{
    my $archive2    = $home->addChild({
                    className => 'WebGUI::Asset::Wobject::StoryArchive',
                    title => 'Uncommitted',
                    url => 'uncommitted_archive',
                  });
    my $guard = WebGUI::Test->cleanupGuard($archive2);
    my $new_folder = $archive2->getFolder;
    is $archive2->get('tagId'), $new_folder->get('tagId'), 'folder added to uncommitted archive uses the same version tag';
}

################################################################
#
#  addChild
#
################################################################

my $child = $archive->addChild({className => 'WebGUI::Asset::Wobject::StoryTopic'});
is($child, undef, 'addChild: Will only add Stories');

$child = $archive->addChild({className => 'WebGUI::Asset::Snippet'});
is($child, undef, '... will not add snippets');

$child = $archive->addChild({className => 'WebGUI::Asset::Wobject::Folder'});
isa_ok($child, 'WebGUI::Asset::Wobject::Folder', '... will add folders, so importing a package works');

$child->purge;

$child = $archive->addChild({className => 'WebGUI::Asset::Story', title => 'First Story'}, @skipAutoCommit);
my $tag1 = WebGUI::VersionTag->getWorking($session);
$tag1->commit;
WebGUI::Test->addToCleanup($tag1);
isa_ok($child, 'WebGUI::Asset::Story', 'addChild added and returned a Story');
is($archive->getChildCount, 1, '... added it to the archive');
my $folder = $archive->getFirstChild();
isa_ok($folder, 'WebGUI::Asset::Wobject::Folder', '... Folder was added to Archive');
is($folder->getChildCount, 1, '... The folder has 1 child...');
is($folder->getFirstChild->getTitle, 'First Story', '... and it is the correct child');

################################################################
#
#  getKeywordFilename
#
################################################################

##Note, this method depends heavily on the default installed language pack.
##Because of that, we'll only test for whether or not url->urlize is called.

is ($archive->getKeywordFilename('camelCase'), 'keyword_camelcase.html', 'getKeywordFilename returns a lower case keyword with _keyword.html appended');

################################################################
#
#  viewTemplateVariables
#
################################################################

my $wgBday    = WebGUI::Test->webguiBirthday;
my $oldFolder = $archive->getFolder($wgBday);

my $yesterday = $now-24*3600;
my $newFolder = $archive->getFolder($yesterday);

my ($wgBdayMorn,undef)    = $session->datetime->dayStartEnd($wgBday);
my ($yesterdayMorn,undef) = $session->datetime->dayStartEnd($yesterday);

my $story = $oldFolder->addChild({ className => 'WebGUI::Asset::Story', title => 'WebGUI is released', keywords => 'roger,foxtrot,echo,all'}, @skipAutoCommit);
$creationDateSth->execute([$wgBday, $story->getId]);
my $tag2 = WebGUI::VersionTag->getWorking($session);
$tag2->commit;
WebGUI::Test->addToCleanup($tag2);

{
    my $storyDB = WebGUI::Asset->newByUrl($session, $story->getUrl);
    is ($storyDB->get('status'), 'approved', 'addRevision always calls for an autocommit');
}

my $pastStory = $newFolder->addChild({ className => 'WebGUI::Asset::Story', title => "Yesterday is history" }, @skipAutoCommit);
$creationDateSth->execute([$yesterday, $pastStory->getId]);
my $tag3 = WebGUI::VersionTag->getWorking($session);
$tag3->commit;
WebGUI::Test->addToCleanup($tag3);

my $templateVars;
$templateVars = $archive->viewTemplateVariables();

cmp_deeply(
    $templateVars,
    superhashof({
        searchHeader => ignore(),
        searchForm   => ignore(),
        searchButton => ignore(),
        searchFooter => ignore(),
    }),
    'viewTemplateVars: search variables present'
);

cmp_deeply(
    $templateVars,
    superhashof({
        rssUrl  => $archive->getRssFeedUrl,
        atomUrl => $archive->getAtomFeedUrl,
    }),
    'viewTemplateVars: RSS and Atom feed template variables'
);

KEY: foreach my $key (keys %{ $templateVars }) {
    next KEY if isIn($key, qw/canPostStories addStoryUrl date_loop mode/);
    delete $templateVars->{$key};
}

$session->user({userId => 1});
cmp_deeply(
    $templateVars,
    {
        canPostStories => bool(0),
        mode           => 'view',
        addStoryUrl    => '',
        date_loop      => [
            {
                epochDate => ignore(),
                story_loop => [ {
                    creationDate => ignore(),
                    url          => re('first-story'),
                    title        => 'First Story',
                }, ],
            },
            {
                epochDate => $yesterdayMorn,
                story_loop => [{
                    creationDate => $yesterday,
                    url          => re('yesterday-is-history'),
                    title        => "Yesterday is history",
                }, ],
            },
            {
                epochDate => $wgBdayMorn,
                story_loop => [ {
                    creationDate => $wgBday,
                    url          => '/home/mystories/august_16_2001/webgui-is-released',
                    title        => 'WebGUI is released',
                }, ],
            },
        ]
    },
    'viewTemplateVariables: returns expected template variables with 3 stories in different folders, user is cannot edit stories'
);

my $story2 = $folder->addChild({ className => 'WebGUI::Asset::Story', title => 'Story 2', keywords => "roger,foxtrot,all"}, @skipAutoCommit);
my $story3 = $folder->addChild({ className => 'WebGUI::Asset::Story', title => 'Story 3', keywords => "foxtrot,echo,all"},  @skipAutoCommit);
my $story4 = $folder->addChild({ className => 'WebGUI::Asset::Story', title => 'Story 4', keywords => "roger,echo,all"},    @skipAutoCommit);
foreach my $storilet ($story2, $story3, $story4) {
    $session->db->write("update asset set creationDate=$now where assetId=?",[$storilet->getId]);
}
$archive->update({storiesPerPage => 3});
my $tag4 = WebGUI::VersionTag->getWorking($session);
$tag4->commit;
WebGUI::Test->addToCleanup($tag4);

##Don't assume that Admin and Visitor have the same timezone.
$session->user({userId => 3});
($wgBdayMorn,undef)   = $session->datetime->dayStartEnd($wgBday);

$templateVars = $archive->viewTemplateVariables();
KEY: foreach my $key (keys %{ $templateVars }) {
    next KEY if isIn($key, qw/canPostStories addStoryUrl date_loop/);
    delete $templateVars->{$key};
}

cmp_deeply(
    $templateVars,
    {
        canPostStories => 1,
        addStoryUrl    => '/home/mystories?func=add;class=WebGUI::Asset::Story',
        date_loop      => [
            {
                epochDate => ignore(),
                story_loop => [
                    {
                        creationDate => ignore(),
                        url          => re('first-story'),
                        title        => 'First Story',
                        editIcon     => ignore(),
                        deleteIcon   => ignore(),
                    },
                    {
                        creationDate => ignore(),
                        url          => ignore(),
                        title        => 'Story 2',
                        editIcon     => ignore(),
                        deleteIcon   => ignore(),
                    },
                    {
                        creationDate => ignore(),
                        url          => ignore(),
                        title        => 'Story 3',
                        editIcon     => ignore(),
                        deleteIcon   => ignore(),
                    },
                ],
            },
        ],
    },
    'viewTemplateVariables: returns expected template variables with several stories in 3 different folders'
);

TODO: {
    local $TODO = "viewTemplateVariables code to write";
    ok(0, 'Check that Stories from the future are not displayed unless the user canEdit this StoryArchive');
}

################################################################
#
#  viewTemplateVariables, keywords search mode
#
################################################################

$session->request->setup_body({ keyword => 'foxtrot' } );
$archive->update({storiesPerPage => 25});

$templateVars = $archive->viewTemplateVariables('keyword');
is($templateVars->{mode}, 'keyword', 'viewTemplateVariables mode == keyword');
cmp_deeply(
    $templateVars->{date_loop},
    [
        {
            epochDate => ignore(),
            story_loop => [
                {
                    creationDate => ignore(),
                    url          => ignore(),
                    title        => 'Story 2',
                    editIcon     => ignore(),
                    deleteIcon   => ignore(),
                },
                {
                    creationDate => ignore(),
                    url          => ignore(),
                    title        => 'Story 3',
                    editIcon     => ignore(),
                    deleteIcon   => ignore(),
                },
            ],
        },
        {
            epochDate => $wgBdayMorn,
            story_loop => [
                {
                    creationDate => ignore(),
                    url          => ignore(),
                    title        => 'WebGUI is released',
                    editIcon     => ignore(),
                    deleteIcon   => ignore(),
                },
            ],
        },
    ],
    'viewTemplateVariables: keyword mode returns the correct assets in the same form as view mode'
);

$archive->update({storiesPerPage => 3});
$session->request->setup_body({ keyword => 'all' } );
$templateVars = $archive->viewTemplateVariables('keyword');
ok($templateVars->{'pagination.pageCount.isMultiple'}, 'keyword search with multiple pages');
is($templateVars->{'pagination.lastPageUrl'}, '/home/mystories?func=view;keyword=all;pn=2', '... pagination variable has correct URL');

$session->request->setup_body({ } );

################################################################
#
#  viewTemplateVariables, search mode
#
################################################################

$session->request->setup_body({ query => 'echo' } );
$archive->update({storiesPerPage => 25});
$templateVars = $archive->viewTemplateVariables('search');
is($templateVars->{mode}, 'search', 'viewTemplateVariables mode == search');

cmp_bag(
    $templateVars->{date_loop},
    [
        {
            epochDate => ignore(),
            story_loop => bag(
                {
                    creationDate => ignore(),
                    url          => ignore(),
                    title        => 'Story 3',
                    editIcon     => ignore(),
                    deleteIcon   => ignore(),
                },
                {
                    creationDate => ignore(),
                    url          => ignore(),
                    title        => 'Story 4',
                    editIcon     => ignore(),
                    deleteIcon   => ignore(),
                },
            ),
        },
        {
            epochDate => $wgBdayMorn,
            story_loop => [
                {
                    creationDate => ignore(),
                    url          => ignore(),
                    title        => 'WebGUI is released',
                    editIcon     => ignore(),
                    deleteIcon   => ignore(),
                },
            ],
        },
    ],
    'viewTemplateVariables: search mode returns the correct assets in the same form as view mode'
);

################################################################
#
#  viewTemplateVariables, export mode
#
################################################################


$session->scratch->set('isExporting', 1);
$archive->update({ storiesPerPage => 3, });
$templateVars = $archive->viewTemplateVariables();
ok( (  !exists $templateVars->{searchHeader}
    && !exists $templateVars->{searchForm}
    && !exists $templateVars->{searchButton}
    && !exists $templateVars->{searchForm}
    ),
    '... export mode, no search variables present'
);

cmp_deeply(
    $templateVars,
    superhashof({
        rssUrl  => $archive->getStaticRssFeedUrl,
        atomUrl => $archive->getStaticAtomFeedUrl,
    }),
    '... export mode, RSS and Atom feed template variables show the static url'
);

my $storyCount = 0;
foreach my $date_loop (@{ $templateVars->{date_loop} }) {
    $storyCount += scalar @{ $date_loop->{story_loop} };
}

cmp_ok($storyCount, '>', 3, '... export mode, pagination increased beyond storiesPerPage');

$session->scratch->delete('isExporting');

################################################################
#
#  tagCloud template variable in view
#
################################################################

$templateVars = $archive->viewTemplateVariables();
my @anchors = simpleHrefParser($templateVars->{keywordCloud});
my @expectedAnchors = ();
foreach my $keyword(qw/echo foxtrot roger all/) {
    push @expectedAnchors, [ $keyword, '/home/mystories?func=view;keyword='.$keyword ];
}
cmp_bag(
    \@anchors,
    \@expectedAnchors,
    'keywordCloud template variable has keywords and correct links',
);

################################################################
#
#  tagCloud template variable in view, exportMode
#
################################################################

$session->scratch->set('isExporting', 1);

$templateVars = $archive->viewTemplateVariables();
@anchors = simpleHrefParser($templateVars->{keywordCloud});
@expectedAnchors = ();
foreach my $keyword(qw/echo foxtrot roger all/) {
    push @expectedAnchors, [ $keyword, '/home/mystories/keyword_'.$keyword.'.html' ];
}
cmp_bag(
    \@anchors,
    \@expectedAnchors,
    '... keywordCloud template variable has keywords and correct links in export mode',
);

$session->scratch->delete('isExporting');

################################################################
#
#  RSS and Atom checks
#
################################################################

is($archive->getRssFeedUrl,  '/home/mystories?func=viewRss',  'RSS Feed Url');
is($archive->getAtomFeedUrl, '/home/mystories?func=viewAtom', 'Atom Feed Url');

$archive->update({itemsPerFeed => 3});

cmp_deeply(
    $archive->getRssFeedItems(),
    [
        {
            title => 'First Story',
            description => ignore(),
            'link'      => ignore(),
            date        => ignore(),
            guid        => ignore(),
            pubDate     => ignore(),
            author      => ignore(),
        },
        {
            title => 'Story 2',
            description => ignore(),
            'link'      => ignore(),
            date        => ignore(),
            author      => ignore(),
            guid        => ignore(),
            pubDate     => ignore(),
        },
        {
            title => 'Story 3',
            description => ignore(),
            'link'      => ignore(),
            date        => ignore(),
            author      => ignore(),
            guid        => ignore(),
            pubDate     => ignore(),
        },
    ],
    'rssFeedItems'
);

################################################################
#
#  export Collateral tests
#
################################################################

my $exportStorage = WebGUI::Storage->create($session);
WebGUI::Test->addToCleanup($exportStorage);
my $basedir = Path::Class::Dir->new($exportStorage->getPath);
$exportStorage->addFileFromScalar('index', 'export story archive content');
my $assetDir  = $basedir->subdir('mystories');
my $assetFile = $assetDir->file('index.html');
mkpath($assetDir->stringify);
mv($exportStorage->getPath('index'), $assetFile->stringify);
$archive->exportAssetCollateral($assetFile, {}, $session);

my $exportedFiles = $exportStorage->getFiles();
cmp_bag(
    $exportedFiles,
    [qw{
        mystories.rss                mystories
        mystories.atom               mystories.rdf
        mystories/index.html         mystories/keyword_echo.html
        mystories/keyword_roger.html mystories/keyword_foxtrot.html
        mystories/keyword_all.html
    }],
    'exportAssetCollateral: feed files exported'
);

cmp_bag(
    [ map { $_->relative($assetDir)->stringify } $assetDir->children ],
    [qw/
        keyword_echo.html
        keyword_roger.html
        keyword_foxtrot.html
        keyword_all.html
        index.html
    /],
    'exportAssetCollateral: keyword files exported into correct dir (below the asset)'
);

my $roger = $exportStorage->getFileContentsAsScalar('mystories/keyword_roger.html');
my @rogerStories = map { $_->[0] } fetchKeywordAssetList($roger);
cmp_bag(
    \@rogerStories,
    [
        'Story 2',
        'Story 4',
        'WebGUI is released',
    ],
    '... contents of roger keyword file'
);

my $foxtrot = $exportStorage->getFileContentsAsScalar('mystories/keyword_foxtrot.html');
my @foxtrotStories = map { $_->[0] } fetchKeywordAssetList($foxtrot);
cmp_bag(
    \@foxtrotStories,
    [
        'Story 2',
        'Story 3',
        'WebGUI is released',
    ],
    '... contents of foxtrot keyword file'
);

my $echo = $exportStorage->getFileContentsAsScalar('mystories/keyword_echo.html');
my @echoStories = map { $_->[0] } fetchKeywordAssetList($echo);
cmp_bag(
    \@echoStories,
    [
        'Story 3',
        'Story 4',
        'WebGUI is released',
    ],
    '... contents of echo keyword file'
);

################################################################
#
#  getKeywordStaticURL
#
################################################################

is($archive->getKeywordStaticURL('foo'), '/home/mystories/keyword_foo.html', 'getKeywordStaticURL: returns absolute URL to keyword file');

$archive->update({ url => '/home/mystories.arch' });
is($archive->getKeywordStaticURL('bar'), '/home/mystories/keyword_bar.html', '... correct URL with file extension');

$archive->update({ url => '/home/mystories' });

################################################################
#
# sortOrder
#
################################################################

my $aaa_child = $archive->addChild({className => 'WebGUI::Asset::Story', title => 'Aaaa'}, @skipAutoCommit);
my $zzz_child = $archive->addChild({className => 'WebGUI::Asset::Story', title => 'Zzzz'}, @skipAutoCommit);
WebGUI::Test->addToCleanup($aaa_child);
WebGUI::Test->addToCleanup($zzz_child);

$archive->update({storiesPerPage => 25, storySortOrder => 'Alphabetically' });

$tag1 = WebGUI::VersionTag->getWorking($session);
$tag1->commit;
WebGUI::Test->addToCleanup($tag1);

$templateVars = $archive->viewTemplateVariables();

cmp_deeply (
    $templateVars->{date_loop},
    [
           {
             'story_loop' => [
                               {
                                 'creationDate' => ignore(),
                                 'deleteIcon' => ignore(),
                                 'editIcon' => ignore(),
                                 'url' => re('aaaa'),
                                 'title' => 'Aaaa'
                               },
                               ignore(),
                               ignore(),
                               ignore(),
                               ignore(),
                             ],
             'epochDate' => ignore(),
           },
           {
             'story_loop' => ignore(),
             'epochDate' => $wgBdayMorn,
           },
           {
             'story_loop' => ignore(),
             'epochDate' => $yesterdayMorn,
           },
          {
             'story_loop' => [
                               {
                                 'creationDate' => ignore(),
                                 'deleteIcon' => ignore(),
                                 'editIcon' => ignore(),
                                 'url' => re('zzzz'),
                                 'title' => 'Zzzz'
                               },
                             ],
             'epochDate' => ignore(),
           },
        ],
        'viewTemplateVariables: sorted by story title'
);

}           ##  end SKIP block

$creationDateSth->finish;

sub simpleHrefParser {
	my ($text) = @_;
	my $p = HTML::TokeParser->new(\$text);
    my @anchors = ();
    while (my $token = $p->get_tag('a')) {
        my $url = $token->[1]{href} || "-";
        my $label = $p->get_trimmed_text("/a");
        push @anchors, [ $label, $url ];
    }
    return @anchors;
}

sub fetchKeywordAssetList {
	my ($text) = @_;
    my @anchors = ();
	my $p = HTML::TokeParser->new(\$text);
    TOKEN: while (my $token = $p->get_tag('ul')) {
        next TOKEN unless $token->[1]->{class} eq 'keywordAssetList';
        while (my $token = $p->get_tag('/ul', 'a')) {
            last TOKEN if $token->[0] eq '/ul';
            my $url = $token->[1]{href} || "-";
            my $label = $p->get_trimmed_text("/a");
            push @anchors, [ $label, $url ];
        }
    }

    return @anchors;
}


