package Text::vFile::Base;

use strict;
use warnings;

=head1 NAME

Text::vFile::Base - Base class for most of the functional classes based on the vCard/vCalendar etc spec.
Most of the hard work in breaking apart lines of data happens using methods in here.

=head1 SYNOPSIS

    package Text::vFoo;

    use Text::vFile::Base;
    use vars qw(@ISA);
    push @ISA, qw(Text::vCard::Base);

    # Tell vFile that BEGIN:VFOO line creates one of these objects
    $Text::vFile::classMap{'VCARD'}=__PACKAGE__;
   
    # Then you will need to create a varHandler - although there are defaults to try
    # and get you going.
    #
    # As well you will need to define more exotic, type specific loaders
    #
    # And finally accessors are your responsibility

=head1 END USAGE

To tell your users how to use this module:

    use Text::vFoo;
    my $loader = Text::vFoo->loader( source => "youppi.vfoo" );

    while (my $vfoo = $loader->next) {
        $vfoo->all_night;
    }

    # or even sexier
    
    while (my $vfoo = <$loader> ) {
        $vfoo->all_night;
    }

It may sound silly, but it should be mentioned. Just becase the user
says:

    my $loader = Text::vFoo->loader( );

Does not mean there will be any complaints if they try to load non-vfoo data.
If the source has vcards in it - that's what they're going to get.

=cut


use overload
        '<>' => \&next,
        fallback => 1,
;

use Carp; $SIG{__DIE__} = \&Carp::confess;
use Data::Dumper; $Data::Dumper::Indent=1; $Data::Dumper::Sortkeys=1;

use lib qw(lib);
use Text::vFile;

our $typeSequence=1;

sub _nextSequence {
    return $typeSequence++;
}

=item @objects = $class->load( key => value )

Calls the Text::vFile load routine. Should slurp all the objects
and return as an array/array ref.

=cut

sub load {
    Text::vFile::Load(@_);
}

=item $loader = $class->loader( key => value )

Returns an object which can return back objects one at a time. Nice
if you want to iterate through a stack of vcards at your leisure.

=cut

sub loader {
    my $class=shift;
    return Text::vFile::new(@_);
}

=back 

=item $object = class->new

Make a new object object that represents this vFile data being parsed.

=cut

sub new {

	my $class = ref($_[0]) ? ref(shift) : shift;
    my $opts = ref($_[0]) ? $_[0] : {@_};

	my $self = bless {}, $class;

    map { $self->$_( $opts->{$_} ) } keys %$opts;

	return $self;

}

=head1 DATA HANDLERS

=over 4

=item varHandler

Returns a hash ref mapping the item label to a handler name. Ie:

   {
        'FN'          => 'singleText',
        'NICKNAME'    => 'multipleText',
        'PHOTO'       => 'singleBinary',
        'BDAY'        => 'singleText',
        'ADR'         => 'ADR',           # Not really necessary
    };

By default if there is no explicit handler then the vFile loader will

   - is there a method called  "load_NAME"?
   - test does the item have type attributes or not
       - yes?   singleTextTyped
       - no?    singleText
   

=cut
 
sub varHandler {
    return {};
}

=item typeDefault

Additional information where handlers require type info. Such as ADR - is this
a home, postal, or whatever? If not supplied the RFC specifies what types they should
default to.

     from vCard:

     {
        'ADR'     => [ qw(intl postal parcel work) ],
        'LABEL'   => [ qw(intl postal parcel work) ],
        'TEL'     => [ qw(voice) ],
        'EMAIL'   => [ qw(internet) ],
    };

=cut

sub typeDefault {
    return {};
}

=item load_singleText

Loads a single text item with no processing other than unescape text

=cut

sub load_singleText { 

	my $val=$_[3];
	$val=~s/\\([\n,])/$1/gs;
    # $val=~s/\\n/\n/gs;
	$_[0]->{$_[1]}{'value'}=$val;
	$_[0]->{$_[1]}{'attr'}=$_[2] if $_[2] && ref($_[2]) eq "HASH" && keys %{$_[2]};

    return $_[0]->{$_[1]};

}

=item load_singleDate

Loads a date creating a DateTime::Format::ICal object. Thanks Dave!

=cut

sub load_singleDate { 

	my $val=$_[3];
    unless (%DateTime::Format::ICal::) {
        eval "use DateTime::Format::ICal";
        warn "Cannot create date/time objects: $@\n" and return if $@;
    }

    eval {
	    $_[0]->{$_[1]}{'value'}=DateTime::Format::ICal->parse_datetime( iso8601 => $val );
    }; if ($@) {
        warn "$val; $@\n";
    }

	$_[0]->{$_[1]}{'attr'}=$_[2] if $_[2] && ref($_[2]) eq "HASH" && keys %{$_[2]};

    return $_[0]->{$_[1]};

}

=item load_singleDuration

Loads a data duration using DateTime::Format::ICal.

=cut

sub load_singleDuration { 

	my $val=$_[3];

    unless (%DateTime::Format::ICal::) {
        eval "use DateTime::Format::ICal";
        warn "Cannot create date/time objects: $@\n" and return if $@;
    }

    eval {
	    $_[0]->{$_[1]}{'value'}=DateTime::Format::ICal->parse_duration( $val );
    }; if ($@) {
        warn "$val; $@\n";
    }

	$_[0]->{$_[1]}{'attr'}=$_[2] if $_[2] && ref($_[2]) eq "HASH" && keys %{$_[2]};

    return $_[0]->{$_[1]};

}

=item load_multipleText

This is text that is separated by commas. The text is then unescaped. An array
of items is created.

=cut

sub load_multipleText {

	my @vals=split /(?<!\\),/, $_[3];
	map { s/\\,/,/ } @vals;

	$_[0]->{$_[1]}{'value'}=\@vals;
	$_[0]->{$_[1]}{'attr'}=$_[2] if $_[2] && ref($_[2]) eq "HASH" && keys %{$_[2]};

    return $_[0]->{$_[1]};

}

=item load_singleTextType

Load text that has a type attribute. Each text of different type attributes
will be handled independantly in as a hash entry. If no type attribute is supplied
then the typeDefaults types will be used. A line can have multiple types. In the
case where multiple types have the same value "_alias" indicators are created.
The preferred type is stored in "_pref"

=cut

sub load_singleTextTyped {
    
    my $typeDefault=$_[0]->typeDefault;

    my $attr=$_[2];

    my %type=();
    map { map { $type{lc $_}=1 } (split /,/, $_) } @{$attr->{'type'}};
    # delete $attr->{'type'};
    map { $type{ lc $_ }=1 } @{$typeDefault->{$_[1]}} unless scalar(keys %type);

    my $item={};
    push @{$_[0]->{$_[1]}}, $item;
    $item->{'value'}=$_[3];
    $item->{'type'}=\%type;
	$item->{'attr'}=$attr if keys %$attr;
    $item->{'sequence'}=_nextSequence();

    return $item;

}

=item load_singleBinary

Not done as I don't have example data yet.

=cut

sub load_singleBinary {
    my ($self, $name, $attr, $value) = @_;

    my $encoding = $attr->{'encoding'} || $attr->{'ENCODING'};

    # type=b means Base64; I don't know about others
    if ($encoding) {

        if (lc $encoding eq "b") {
            eval "use MIME::Base64";
            warn "Cannot decode binary MIME encoded objects: $@\n" and return if $@;
            $self->{$name}{'value'} = MIME::Base64::decode_base64($value);
        } else {
            warn "Unknown encoding $encoding for $name\n";
            return undef;
        }

    } else {
        # This must be an URL

    }
	$self->{$name}{'attr'}=$attr if $attr && ref($attr) eq "HASH" && keys %{$attr};

	die "_singleBinary not done\n";
}



1;

