package DateTime::Language;

use strict;

use Class::Factory::Util;
use Params::Validate qw( validate SCALAR );
use vars qw ( %ISOMap );

# my %ISOMap;
foreach my $set ( [ 'aa', 'aar'                 => 'Afar' ],
                  [ 'am', 'amh'                 => 'Amharic' ],
                  [ 'cz', 'ces', 'cze'          => 'Czech' ],
                  [ 'de', 'deu', 'ger'          => 'German' ],
                  [ 'de-at', 'deu-at', 'ger-at' => 'Austrian' ],
                  [ 'dk', 'dan'                 => 'Danish' ],
                  [ 'en', 'eng'                 => 'English' ],
                  [ 'es', 'esl', 'spa'          => 'Spanish' ],
                  [ 'fr', 'fra', 'fre'          => 'French' ],
                  [ 'x-drs', 'sil-drs'          => 'Gedeo' ],
                  [ 'it', 'ita'                 => 'Italian' ],
                  [ 'nl', 'dut', 'nla'          => 'Dutch' ],
                  [ 'no', 'nor'                 => 'Norwegian' ],
                  [ 'om', 'orm'                 => 'Oromo' ],
                  # not quite right, but better than failing
                  [ 'pt', 'por'                 => 'Brazilian' ],
                  [ 'pt-br', 'por-br'           => 'Brazilian' ],
                  [ 'sid'                       => 'Sidama' ],
                  [ 'so', 'som'                 => 'Somali' ],
                  [ 'sv', 'sve', 'swe'          => 'Swedish' ],
                  [ 'ti-et', 'tig-et'           => 'TigrinyaEthiopian' ],
                  [ 'ti-er', 'tig-er'           => 'TigrinyaEritrean' ],
             )
{
    my $module = pop @$set;
    @ISOMap{ @$set } = ($module) x @$set;
}

# print "ISOMap: $ISOMap{sid}\n";

sub new
{
    my $class = shift;
    my %p = validate( @_,
                      { language => { type => SCALAR } },
                    );

    my $real_class = $class->load( $p{language} );

    my $obj = bless {}, $real_class;

    $obj->_init;

    return $obj;
}

sub languages { $_[0]->subclasses }
sub iso_codes { keys %ISOMap }

sub class_base
{
	"DateTime::Language";
}


sub load
{
    my $class = shift;
    my $lang = shift;

    my $real_lang;
    if ( $lang =~ /^((?:x-)?\w\w\w?)(?:-\w\w\w?)?$/ )
    {
        $real_lang =
            ( exists $ISOMap{$lang} ?
              $ISOMap{$lang} :
              $1 ?
              $ISOMap{$1} :
              undef
            );

        die "Unsupported or invalid ISO language code, $lang"
            unless defined $real_lang;
    }
    else
    {
        $real_lang = $lang;
    }

    my $real_class = $class->class_base."::$real_lang";
    eval "use $real_class";
    die $@ if $@;

    # print "XLoading $real_class\n";
    return $real_class;
}

sub _init
{
    my $self = shift;
    my $class = ref $self;

    foreach my $key ( qw( day_names day_abbreviations month_names
                          month_abbreviations am_pm ordinal_suffixes
                          month_numbers day_numbers
                        )
                    )
    {
        my $var_name = join '', map { ucfirst } split /_/, $key;
        $var_name = 'AMPM' if $var_name eq 'AmPm';

        no strict 'refs';
        if ( $key =~ /numbers$/ )
        {
            $self->{$key} = \%{"$class\::$var_name"};
        }
        else
        {
            $self->{$key} = \@{"$class\::$var_name"};
        }
    }
}

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

sub month_name  { $_[0]->{month_names}[ $_[1]->month_0 ] }

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

sub day_name  { $_[0]->{day_names}[ $_[1]->day_of_week_0 ] }

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

sub month_abbreviation  { $_[0]->{month_abbreviations}[ $_[1]->month_0 ] }

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

sub day_abbreviation  { $_[0]->{day_abbreviations}[ $_[1]->day_of_week_0 ] }

sub am_pm_list { $_[0]->{am_pm} }

sub am_pm { $_[0]->{am_pm}[ $_[1]->hour < 12 ? 0 : 1 ] }

#sub preferred_datetime_format { '%m/%d/%y %H:%M:%S' }
#sub preferred_date_format { '%m/%d/%y' }
#sub preferred_time_format { '%H:%M:%S' }

1;

__END__

=head1 NAME

DateTime::Language - base class for DateTime.pm-related language localization

=head1 SYNOPSIS

  package DateTime::Language::Gibberish;

  use base qw(DateTime::Language);

=head1 DESCRIPTION

This class provides most of the methods needed to implement language
localization for DateTime.pm.  A subclass of this language simply
provides a set of data structures containing things like day and
months names.

This module is a factory for language subclasses, and can load a class
either based on the language portion of its name, such as "English",
or based on its ISO code, such as "en" or "eng".

=head1 USAGE

This module provides the following methods:

=over 4

=item * new( language => $language )

This method loads the requested language and returns an object of the
appropriate class.  The "language" parameter may be the name of the
language subclass to be used, such as "English", as returned by the
C<languages()> method.  It can also be an ISO 639 two-letter language
code.  The language code may include an ISO 3166 two-letter country
after a dash, so things like "en" or "en-us" are both legal.  If a
country code is given, then the most specific match is used.  For
example, if "en-au" (English, Australian) is given, then the nearest
match will be "en", which will be used instead.

=item * load( $language )

This tells the module to load the specified language without creating
an object.  The language given can be anything accepted by the
C<new()> method.

=item * languages

Returns a list of supported language names.

=item * iso_codes

Returns a list of supported ISO language codes.  See the C<new()>
method documentation for details.

=back

=head1 SUBCLASSING

People who want to add support for new languages may be interested in
subclassing this module.

The simplest way to do this is to simply declare your new module,
let's call it C<DateTime::Language::Inhumi>, a subclass of
C<DateTime::Language>, and to define a set of global variables in your
namespace.

These globals are:

=over 4

=item * @DayNames

The names of each day, starting with Monday.

=item * @DayAbbreviations

Abbreviated names for each day.

=item * @MonthNames

The names of each month, starting with January.

=item * @MonthAbbreviations

Abbreviated names for each month.

=item * @AMPM

The terms used for AM and PM in the language you are implementing.

=back

The C<DateTime::Language> module implements methods that use these
globals as needed.  If you need to implement more complex algorithms,
you can override the following methods:

=over 4

=item * month_names

Returns a list of month names.

=item * month_name( $dt )

Given a C<DateTime> object, this method should return the correct
month name.

=item * month_abbreviations

Returns a list of month abbreviations.

=item * month_abbreviation( $dt )

Given a C<DateTime> object, this method should return the correct
month abbreviation.

=item * day_names

Returns a list of day names.

=item * day_name( $dt )

Given a C<DateTime> object, this method should return the correct day
name.

=item * day_abbreviations

Returns a list of day abbreviations.

=item * day_abbreviation( $dt )

Given a C<DateTime> object, this method should return the correct day
abbreviation.

=item * am_pm_list

Returns a list of the AM/PM texts. First item should be the AM, the
second should be the PM.

=item * am_pm( $dt )

Given a C<DateTime> object, returns the correct AM or PM abbreviation.

=back

=head1 SUPPORT

Support for this module is provided via the datetime@perl.org email
list.  See http://lists.perl.org/ for more details.

=head1 AUTHOR

Dave Rolsky <autarch@urth.org>

However, please see the CREDITS file for more details on who I really
stole all the code from.

=head1 COPYRIGHT

Copyright (c) 2003 David Rolsky.  All rights reserved.  This program
is free software; you can redistribute it and/or modify it under the
same terms as Perl itself.

Portions of the code in this distribution are derived from other
works.  Please see the CREDITS file for more details.

The full text of the license can be found in the LICENSE file included
with this module.

=head1 SEE ALSO

datetime@perl.org mailing list

http://datetime.perl.org/

=cut