package DateTime::TimeZone::OlsonDB::Zone;

use strict;
use warnings;

use DateTime::TimeZone;
use DateTime::TimeZone::OlsonDB;
use DateTime::TimeZone::OlsonDB::Change;
use DateTime::TimeZone::OlsonDB::Observance;

use List::Util qw( first );
use Params::Validate qw( validate SCALAR ARRAYREF );

sub new
{
    my $class = shift;
    my %p = validate( @_, { name => { type => SCALAR },
                            observances => { type => ARRAYREF },
                            olson_db => 1,
                          }
                    );

    my $self = { name => $p{name},
                 observances => $p{observances},
                 changes => [],
                 infinite_rules => {},
               };

    return bless $self, $class;
}

sub name { $_[0]->{name} }

sub expand_observances
{
    my $self = shift;
    my $odb = shift;
    my $max_year = shift;

    my $prev_until;
    for ( my $x = 0; $x < @{ $self->{observances} }; $x++ )
    {
        my %p = %{ $self->{observances}[$x] };

        my $rules_name = delete $p{rules};

        my $last_offset_from_std =
            $self->last_change ? $self->last_change->offset_from_std : 0;
        my $last_offset_from_utc =
            $self->last_change ? $self->last_change->offset_from_utc : 0;

        my $obs =
            DateTime::TimeZone::OlsonDB::Observance->new
                ( %p,
                  utc_start_datetime => $prev_until,
                  rules => [ $odb->rules_by_name($rules_name) ],
                  last_offset_from_utc => $last_offset_from_utc,
                  last_offset_from_std => $last_offset_from_std,
                );

        my $rule = $obs->first_rule;
        my $letter = $rule ? $rule->letter : '';

        my $change =
            DateTime::TimeZone::OlsonDB::Change->new
                ( type => 'observance',
                  utc_start_datetime   => $obs->utc_start_datetime,
                  local_start_datetime => $obs->local_start_datetime,
                  short_name => sprintf( $obs->format, $letter ),
                  observance => $obs,
                  $rule ? ( rule => $rule ) : (),
                );

        if ($DateTime::TimeZone::OlsonDB::DEBUG)
        {
            print "Adding observance change ...\n";

            $change->_debug_output;
        }

        $self->add_change($change);

        if ( $obs->rules )
        {
            $obs->expand_from_rules( $self, $max_year );
        }

        $prev_until =
            $obs->until( $self->last_change ? $self->last_change->offset_from_std : 0 );

        # last observance
        if ( $x == $#{ $self->{observances} } )
        {
            foreach my $rule ( $obs->rules )
            {
                if ( $rule->is_infinite )
                {
                    $self->add_infinite_rule($rule);
                }
            }
        }
    }
}

sub add_change
{
    my $self = shift;
    my $change = shift;

    if ( defined $change->utc_start_datetime )
    {
        if ( @{ $self->{changes} }
             && $self->{changes}[-1]->utc_start_datetime
             && $self->{changes}[-1]->utc_start_datetime == $change->utc_start_datetime
           )
        {
            if ( $self->{changes}[-1]->rule && $change->observance )
            {
                print " Ignoring previous rule change, that starts the same time as current observance change\n\n"
                    if $DateTime::TimeZone::OlsonDB::DEBUG;

                $self->{changes}[-1] = $change;

                return;
            }

            die "Cannot add two different changes that have the same UTC start datetime!\n";
        }

        my $last_change = $self->last_change;

        if ( $last_change->short_name eq $change->short_name
             && $last_change->total_offset == $change->total_offset
             && $last_change->is_dst == $change->is_dst
             && $last_change->observance eq $change->observance
           )
        {
            my $last_rule = $last_change->rule || '';
            my $new_rule = $change->rule || '';

            if ( $last_rule eq $new_rule )
            {
                print "Skipping identical change\n" if $DateTime::TimeZone::OlsonDB::DEBUG;

                return;
            }
        }

        push @{ $self->{changes} }, $change;
    }
    else
    {
        if ( $self->{earliest} )
        {
            die "There can only be one earliest time zone change!";
        }
        else
        {
            $self->{earliest} = $change;
        }
    }
}

sub add_infinite_rule
{
    $_[0]->{infinite_rules}{ $_[1] } = $_[1];
}

sub last_change { return unless @{ $_[0]->{changes} } || $_[0]->{earliest};
                  return ( @{ $_[0]->{changes} } ?
                           $_[0]->{changes}[-1] :
                           $_[0]->{earliest} ); }

sub sorted_changes { ( ( defined $_[0]->{earliest} ? $_[0]->{earliest} : () ),
                       sort { $a->utc_start_datetime <=> $b->utc_start_datetime }
                       @{ $_[0]->{changes} } ) }

sub infinite_rules { values %{ $_[0]->{infinite_rules} } }

1;
