#------------------------------------------------------------
# Copyright (C) 2006 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------
package BookPub::Analytics::BaseReport;
use strict;
use warnings;
use Data::Dumper;

use lib '/app/tools/appuser/lib';
use AppUser::User::User;

use lib '/app/tools/common/lib';
use RSApache::Response::XSLT;
use Common::Assert;
use Common::XMLObject;

#use Common::Util;
use Common::Client;
use Common::Consts qw(%CODE_TO_COUNTRY);

use lib '/app/tools/data_classes/lib';
use Period::QuarterPeriod;
use Period::MonthPeriod;
use Period::YearPeriod;

use lib '/app/tools/bookpub/lib';
use BookPub::Analytics::ReportLib;    # for pie charts
use BookPub::Stats::Sales;
use BookPub::DB::Item::Contributor;
use BookPub::DB::Item::Sale;
use BookPub::DB::Item::Imprint;
use BookPub::DB::Item::Publisher;
use BookPub::DB::Item::PublisherImprints;
use BookPub::DB::Item::BookRelated;
use BookPub::DB::Item::BookContributor;
use BookPub::DB::Item::ChapterRelated;
use BookPub::DB::Item::Service;
use BookPub::DB::Item::AggSaleLog;

use base 'Common::XMLObject';

use constant MAX_RECORDS      => 200;
use constant RECORDS_PER_PAGE => 20;
use constant PROD_BOOK        => 'bk';
use constant PROD_CHAP        => 'ch';

use Exporter 'import';
use vars qw(@EXPORT);
@EXPORT = qw(MAX_RECORDS RECORDS_PER_PAGE PROD_BOOK PROD_CHAP);

sub _init {
    my ( $self, %args ) = @_;

    $self->SUPER::_init(%args);

    # !!! Note -
    # _Loads_ of refactoring to do here.
    # Need a consistent object model interface for Period, Pie Charts, Label, etc.
    #

    # Initialize the Period object.
    #
    my $periodType = $args{periodType};
    assert( 'm' eq $periodType || 'q' eq $periodType || 'y' eq $periodType, "Bogus period parameter $periodType" );

    my $period;
    my $endPeriod;
    if ( 'm' eq $periodType ) {
        $period = Period::MonthPeriod->new();
        if ($args{endPeriodParam}) {
            $endPeriod = Period::MonthPeriod->new();
            $self->{_endPeriod} = $endPeriod;
        }
    } elsif ( 'q' eq $periodType ) {
        $period = Period::QuarterPeriod->new();
    } elsif ( 'y' eq $periodType ) {
        $period = Period::YearPeriod->new();
    }

    # We'll need the period object later.
    # !!! some day we won't need to split the object and it's xml...
    #
    $self->{_period} = $period;

    # Finish initializing Period
    #
    my $periodParam    = $args{periodParam};
    my $endPeriodParam = $args{endPeriodParam};
    if ($periodParam) {
        $self->{_period}->Load( period_id => $periodParam, end_period => $endPeriodParam );
        if ($endPeriodParam) {
            $self->{_endPeriod}->Load( period_id => $endPeriodParam );
        }
    } else {
        #$self->{_period}->LoadCurrent();
        return $self unless ( $self->{_period}->LoadCurrent() );
    }

    $self->{Period} = $period->GetObjectXML();

    if ($endPeriod) {
        $self->{EndPeriod} = $endPeriod->GetObjectXML();
    }

    # Load agg_sale_log dates for RS Admin display
    eval {
        my $dates = BookPub::DB::Item::AggSaleLog->GetLastDates();
        $self->{AggSaleLog}{LastChecked}   = $dates->{last_checked};
        $self->{AggSaleLog}{LastRefreshed} = $dates->{last_refreshed};
    };
    if ($@) {
        print STDERR "BookPub::Analytics::BaseReport - failed to load AggSaleLog dates: $@\n";
    }

    # Different subclasses will need slightly different initialization of the
    # stats object.  But many do exactly the same thing.  So I've put that code
    # in a method that they can override if they wish.
    #
    $self->{_statParams} = $self->_initStatsParams(%args);
    $self->{_stats}      = BookPub::Stats::Sales->new( %{ $self->{_statParams} } );

    # !!! It appears that we can't re-use the stats object used to fill out the
    # !!! main body of the report here, where we set up the period nav xml.
    # !!! I assume this is because the report's stats may be constrained.
    #
    my $navStats = BookPub::Stats::Sales->new(
        period_id   => $self->{_period}->PeriodID,
        period_type => $self->{_period}->PeriodType,
        client_id   => Common::RSApp::GetClientID(),
    );

    my $periods  = $navStats->GetPeriodNavParams();
    my $monthMap = _getMonthMap();

    my $mEnd = $periods->{start_month};
    my $mid = $periods->{max_m} - ( $mEnd - 1 );

    my $qEnd = int( $mEnd / 3 );
    $qEnd++ if ( $mEnd % 3 );
    my $qid = $periods->{max_q} - ( $qEnd - 1 );

    for ( my $y = $periods->{max_y} ; $y >= $periods->{min_y} ; $y-- ) {
        my @item = ();
        for ( my $q = 1 ; $q <= $qEnd ; $q++ ) {
            if ( $qid < $periods->{min_q} ) {
                $qid++;
                next;
            }
            push( @item, { Name => "Q$q", ID => 'q' . $qid++ } );
        }
        for ( my $m = 1 ; $m <= $mEnd ; $m++ ) {
            if ( $mid < $periods->{min_m} ) {
                $mid++;
                next;
            }
            push( @item, { Name => $monthMap->{$m}, ID => 'm' . $mid++ } );
        }
        push(
            @{ $self->{PeriodNav}{Year} },
            {
                ID   => "y$y",
                Year => $y,
                Item => \@item,
            }
        );

        $mid -= $mEnd + 12;
        $mEnd = 12;

        $qid -= $qEnd + 4;
        $qEnd = 4;
    }

    # we always need sub imprint xml, unless this user is
    # limited to their own imprint
    my $allowedImprints;
    my $listOfImprints = $args{commandObj}->_getUser()->getImprintIDs();
    if ( $listOfImprints && scalar @$listOfImprints > 0 ) {
        $allowedImprints = {};
        foreach my $allowedImprintID (@$listOfImprints) {
            $allowedImprints->{$allowedImprintID} = 1;
        }

        if ( scalar @$listOfImprints == 1 ) {
            foreach my $imprintID (@$listOfImprints) {
                my $imprint = BookPub::DB::Item::Imprint->Lookup( imprint_id => $imprintID );
                $self->{Imprint} = { ImprintID => $imprintID, ImprintName => $imprint->name };
            }
        }

    }

    my $imprint_param = $args{imprintID};

    # If a user is trying to view an imprint that they shouldn't,
    # let's throw an error.
    #
    if ( $imprint_param && defined $allowedImprints && !$allowedImprints->{$imprint_param} ) {
        die "ERROR - user is not allowed access to imprint id $imprint_param";
    }

    my $imprints = BookPub::DB::Item::Imprint->GetAllSortedByName();
    while ( $imprints->hasNext() ) {
        my $i = $imprints->next();

        if ( defined $allowedImprints && !$allowedImprints->{ $i->imprint_id } ) {
            next;
        }

        my ( $iname, $pretty_name );
        $iname = $pretty_name = $i->name;

        if ( $imprint_param and $imprint_param eq $i->imprint_id ) {
            $self->{Imprint} = { ImprintID => $imprint_param, ImprintName => $iname };
        }

        if ( length $pretty_name > 40 ) {
            $pretty_name = substr $pretty_name, 0, 37;
            $pretty_name .= "...";
        }
        push @{ $self->{Imprints}{Imprint} },
          {
            ImprintID         => $i->imprint_id,
            ImprintName       => $iname,
            ImprintNamePretty => $pretty_name
          };
    }

    my $allowedPublishers;
    my $listOfPublishers = $args{commandObj}->_getUser()->getPublisherIDs();
    if ( $listOfPublishers && scalar @$listOfPublishers > 0 ) {
        $allowedPublishers = {};
        foreach my $allowedPublisherID (@$listOfPublishers) {
            $allowedPublishers->{$allowedPublisherID} = 1;
        }

        if ( scalar @$listOfPublishers == 1 ) {
            foreach my $publisherID (@$listOfPublishers) {
                my $publisher = BookPub::DB::Item::Publisher->Lookup( publisher_id => $publisherID );
                $self->{Publisher} = { PublisherID => $publisherID, PublisherName => $publisher->name };
            }
        }
    }

    my $publisher_param = $args{publisherID};

    # If a user is trying to view an publisher that they shouldn't,
    # let's throw an error.
    #
    if ( $publisher_param && defined $allowedPublishers && !$allowedPublishers->{$publisher_param} ) {
        die "ERROR - user is not allowed access to publisher id $publisher_param";
    }

    my $publisherID       = -1;
    my $publisherImprints = BookPub::DB::Item::PublisherImprints->GetAllSortedByName();
    while ( $publisherImprints->hasNext() ) {
        my $pi = $publisherImprints->next();

        if ( defined $allowedPublishers && !$allowedPublishers->{ $pi->publisher_id } ) {
            next;
        }

        my $pretty_name;
        if ( $publisherID != $pi->publisher_id ) {
            $publisherID = $pi->publisher_id;
            $pretty_name = $pi->publisher_name;

            if ( length($pretty_name) > 40 ) {
                $pretty_name = substr( $pretty_name, 0, 37 ) . '...';
            }

            push(
                @{ $self->{Publishers}{Publisher} },
                {
                    PublisherID         => $pi->publisher_id,
                    PublisherName       => $pi->publisher_name,
                    PublisherNamePretty => $pretty_name
                }
            );

            if ( $publisher_param and $publisher_param eq $publisherID ) {
                $self->{Publisher} = { PublisherID => $publisher_param, PublisherName => $pi->publisher_name };

                # !!! Maybe not.  We can still show All Imprints for a publisher they have access to.
                #
                # If the publisher selection is restricted and we haven't been sent an imprint id,
                # we need to pick the first one for this publisher.
                #
                #if (defined $allowedPublishers && !$self->{Imprint})
                #{
                #    $self->{Imprint} = {ImprintID => $pi->imprint_id, ImprintName => $pi->imprint_name};
                #}

            }
        }

        $pretty_name = $pi->imprint_name;
        if ( length($pretty_name) > 40 ) {
            $pretty_name = substr( $pretty_name, 0, 37 ) . '...';
        }

        push(
            @{ $self->{Publishers}{Publisher}[-1]{Imprints}{Imprint} },
            {
                ImprintID         => $pi->imprint_id,
                ImprintName       => $pi->imprint_name,
                ImprintNamePretty => $pretty_name
            }
        );
    }

    my $services = BookPub::DB::Item::Service->GetAllSortedByName();
    while ( $services->hasNext() ) {
        my $s = $services->next();
        push @{ $self->{Services}->{Service} }, { ServiceID => $s->service_id, ServiceName => $s->service_name };
    }

    # country info
    my $geo_map = _mapCountries( $navStats->GetCountryList( all => 1 ) );
    foreach my $ccode ( sort { $geo_map->{$a} cmp $geo_map->{$b} } keys %$geo_map ) {
        push @{ $self->{Countries}->{Country} }, { Code => uc($ccode), Name => $geo_map->{ uc($ccode) } };
    }
    my $current_country = uc( $args{countryCode} );
    $self->{Country} = $CODE_TO_COUNTRY{$current_country} || $current_country;

    return $self;
}

sub _initStatsParams {
    my ( $self, %args ) = @_;

    # This hash of parameters gets re-used several times.
    # !!! Why, I don't really understand.  That needs to be fixed.
    #
    my %params = (
        period_id     => $self->{_period}->PeriodID,
        period_type   => $self->{_period}->PeriodType,
        end_period_id => $args{endPeriodParam},
        client_id     => Common::RSApp::GetClientID(),
        imprint_id    => $args{imprintID},
        publisher_id  => $args{publisherID},
        country_code  => $args{countryCode},
        user_id       => $args{commandObj}->_getUser()->UserID(),
    );

    return \%params;
}

#---------------------------------------
# helper functions
#---------------------------------------

sub _getPageOffset {
    my ( $self, $page_num ) = @_;

    $page_num = 1 unless $page_num;

    my $max_pages = MAX_RECORDS / RECORDS_PER_PAGE;

    $page_num = 1          if ( $page_num < 1 );
    $page_num = $max_pages if ( $page_num > $max_pages );

    return ( $page_num - 1 ) * RECORDS_PER_PAGE;
}

sub _getPageCount {
    my ( $record_count, $offset ) = @_;
    my $pages = MAX_RECORDS / RECORDS_PER_PAGE;

    if ( $record_count + $offset < MAX_RECORDS ) {
        $pages = int( ( $record_count + $offset ) / RECORDS_PER_PAGE );
        $pages++ if ( ( $record_count + $offset ) % RECORDS_PER_PAGE );
    }

    return $pages;
}

sub _getTopProducts {
    my $self        = shift;
    my %args        = @_;
    my $stats_obj   = $args{stats_obj};
    my $prod_type   = $args{prod_type};
    my @metric_list = map { $_->ID } @{ $args{metric_list} };

    my $table_data = [];
    my ( $products, $count ) = $stats_obj->GetTopRanking(
        category     => 'product',
        product_type => $prod_type,
        limit        => $args{limit} || RECORDS_PER_PAGE,
    );
    my $productsPrev = $stats_obj->GetTopRanking( category => 'product', product_type => $prod_type, prev_rank => 1 );

    foreach my $prodRef (@$products) {
        my $prod_id  = $prodRef->ID;
        my $row_data = {};

        $row_data->{Rank}     = $prodRef->Rank;
        $row_data->{Revenue}  = Common::Client::Current()->Locale()->formatMoney( $prodRef->Revenue );
        $row_data->{Units}    = Common::Client::Current()->Locale()->formatNumber( $prodRef->Units );
        $row_data->{PrevRank} = $productsPrev->{$prod_id} || '';

        if ( $prod_type eq PROD_BOOK ) {
            $row_data->{ProductInfo} = $self->_getBookInfo( product_id => $prod_id );
        } elsif ( $prod_type eq PROD_CHAP ) {
            $row_data->{ProductInfo} = $self->_getChapterInfo( product_id => $prod_id );
        }

        if (@metric_list) {
            $row_data->{Data} = [];

            my $summary_metric =
              $stats_obj->GetSummaryMetric( category => $args{metric_name}, metric_list => \@metric_list, product_id => $prod_id );
            foreach my $metric_id (@metric_list) {

                # !!! Have to localize the result set.
                # It would be more efficient to do that in the Stats object, but that would almost
                # certainly break something else..
                #
                push @{ $row_data->{Data} }, Common::Client::Current()->Locale()->formatMoney( $summary_metric->{$metric_id} );
            }
        }

        push @$table_data, $row_data;
    }

    if ( $table_data && ( scalar @$table_data >= 1 ) ) {
        my @data_labels = @metric_list ? map { $args{metric_hash}->{$_} } @metric_list : undef;
        my $table_href = CreateTableXML( data => $table_data, labels => \@data_labels );

        if ($table_href) {
            if ( $prod_type eq PROD_BOOK ) {
                $table_href->{TableName} = 'top_books';
            } elsif ( $prod_type eq PROD_CHAP ) {
                $table_href->{TableName} = 'top_chapters';
            }
            push @{ $args{tables} }, $table_href;
        }
    }

    return $count;
}

sub _getBookInfo {
    my ( $self, %args ) = @_;
    my $book = BookPub::DB::Item::BookRelated->Lookup( product_id => $args{product_id} );
    my @authors = BookPub::DB::Item::BookContributor->GetAuthorNamesByBookID( $book->book_id );

    return {
        ProductID => $args{product_id},
        BookID    => $book->book_id,
        Title     => $book->title,
        SubTitle  => $book->subtitle,
        Edition   => $book->edition,
        ISBN10    => $book->isbn10,
        ISBN13    => $book->isbn13,
        Authors   => \@authors,
    };
}

sub _getChapterInfo {
    my ( $self, %args ) = @_;
    my $chapter = BookPub::DB::Item::ChapterRelated->Lookup( product_id => $args{product_id} );
    return {
        ProductID    => $args{product_id},
        BookID       => $chapter->book_id,
        BookTitle    => $chapter->book_title,
        ChapterID    => $chapter->chapter_id,
        ChapterTitle => $chapter->chapter_title,
        Ordinal      => $chapter->ordinal,
        AuthorName   => $chapter->author_name,
        AuthorID     => $chapter->author_id,
    };
}

sub _getTopAuthors {
    my $self        = shift;
    my %args        = @_;
    my $stats_obj   = $args{stats_obj};
    my @metric_list = map { $_->ID } @{ $args{metric_list} };

    my $table_data = [];
    my ( $authors, $count ) = $stats_obj->GetTopRanking(
        category => 'author',
        limit    => $args{limit} || RECORDS_PER_PAGE,
    );
    my $authorsPrev = $stats_obj->GetTopRanking( category => 'author', prev_rank => 1 );

    foreach my $authorRef (@$authors) {
        my $author_id = $authorRef->ID;
        my $row_data  = {};

        $row_data->{Rank}     = $authorRef->Rank;
        $row_data->{Revenue}  = Common::Client::Current()->Locale()->formatMoney( $authorRef->Revenue );
        $row_data->{Units}    = Common::Client::Current()->Locale()->formatNumber( $authorRef->Units );
        $row_data->{PrevRank} = $authorsPrev->{$author_id} || '';

        my $author_info = BookPub::DB::Item::Contributor->Lookup( contributor_id => $author_id );
        $row_data->{AuthorInfo}->{AuthorName} = $author_info->name_inverted;
        $row_data->{AuthorInfo}->{AuthorID}   = $author_id;

        if (@metric_list) {
            $row_data->{Data} = [];

            my $summary_metric =
              $stats_obj->GetSummaryMetric( category => $args{metric_name}, metric_list => \@metric_list, author_id => $author_id );
            foreach my $metric_id (@metric_list) {
                push @{ $row_data->{Data} }, Common::Client::Current()->Locale()->formatMoney( $summary_metric->{$metric_id} );

                #                push @{ $row_data->{Data} }, $summary_metric->{$metric_id} || 0;
            }
        }

        push @$table_data, $row_data;
    }

    if ( $table_data && ( scalar @$table_data >= 1 ) ) {
        my @data_labels = @metric_list ? map { $args{metric_hash}->{$_} } @metric_list : undef;
        my $table_href = CreateTableXML( data => $table_data, labels => \@data_labels );

        if ($table_href) {
            $table_href->{TableName} = 'top_authors';
            push @{ $args{tables} }, $table_href;
        }
    }

    return $count;
}

sub _getImprintMap {
    my $self     = shift;
    my %imprints = ();
    map { $imprints{ $_->{ImprintID} } = $_->{ImprintName}; } @{ $self->{Imprints}{Imprint} };

    return \%imprints;

}

sub _getServiceMap {
    my $self     = shift;
    my %services = ();
    map { $services{ $_->{ServiceID} } = $_->{ServiceName}; } @{ $self->{Services}{Service} };

    return \%services;
}

sub _sumRevenue {
    my $metric = shift;
    my $sum    = 0;
    map { $sum += $_->Revenue } @$metric;

    return $sum;
}

sub _mapCountries {
    my $country_list = shift;
    my %country_map  = ();

    map { $country_map{$_} = $CODE_TO_COUNTRY{$_} || $_; } @$country_list;

    return \%country_map;
}

sub _getMonthMap {
    return {
        1  => 'Jan',
        2  => 'Feb',
        3  => 'Mar',
        4  => 'Apr',
        5  => 'May',
        6  => 'Jun',
        7  => 'Jul',
        8  => 'Aug',
        9  => 'Sep',
        10 => 'Oct',
        11 => 'Nov',
        12 => 'Dec',
    };
}

1;
