#==============================================================================a#

package Mac::Tie::PList;
our $VERSION = '0.03';

#==============================================================================a#

=head1 NAME

Mac::Tie::PList - Parse Apple NSDictionary objects (e.g. Preference Lists)
 

=head1 SYNOPSIS

use Mac::Tie::PList;

    my $plist = Mac::Tie::PList->new_from_file("/Library/Preferences/.GlobalPreferences.plist");

    while ((my $key,$val) = each %$plist) {
    	print "$key => $val\n";
    }


=head1 DESCRIPTION

This module allows you to parse NSDictionary objects, as used in PList files, as tied perl
objects. It uses the L<Foundation> perl/objective-c bridge and so both xml1 and binary1
formats are currently supported.

The objects are mapped as follows:

	NSNumber NSBoolean NSString => perl tied scalar
	NSArray => perl tied array
	NSDictionary => perl tied hash
	NSDate => perl tied string - returns seconds since 1970
	NSData => *WARNING* The returned sting format is not decided yet
	
NOTE: Currently the module only provided read access to the data. Write access is
planned in the future.

=over 4

=cut

#==============================================================================a#

use strict;
use warnings;
use Carp;
use Foundation;
use File::Temp qw(tempfile);

=item my $hash_ref = Mac::Tie::PList->new($data)

Parses data and creates a new tied hash based on the data provided as a string.

=cut

sub new {
	my ($obj,$xml) = @_;
	my ($tmp_fh,$tmp_file) = tempfile();
	$tmp_fh->print($xml);
	$tmp_fh->close();
	my $plist = $obj->new_from_file($tmp_file);
	unlink($tmp_file);
	return $plist;
}


=item my $hash_ref = Mac::Tie::PList->new_from_file($filename)

Parses data and creates a new tied hash based on the contents of a file.

=cut

	
sub new_from_file {
	my ($obj,$file) = @_;
	my $plist_obj = NSDictionary->dictionaryWithContentsOfFile_($file);
	if ($plist_obj && $$plist_obj)  {
		tie my %plist, 'Mac::Tie::PList::Hash', $plist_obj;
		return \%plist;
	} else {
		return;
	}
}


sub _tie_plist {
	my ($plist_obj) = @_;
	if ($plist_obj->isKindOfClass_(NSArray->class) ) {
		tie my @plist, 'Mac::Tie::PList::Array', $plist_obj;
		return \@plist;
	} elsif (
		($plist_obj->isKindOfClass_(NSCFNumber->class)) ||
		($plist_obj->isKindOfClass_(NSCFBoolean->class)) ||
		($plist_obj->isKindOfClass_(NSCFData->class)) ||
		($plist_obj->isKindOfClass_(NSDate->class)) ||
		($plist_obj->isKindOfClass_(NSCFString->class)) 
	) {
		tie my $plist, 'Mac::Tie::PList::Scalar', $plist_obj;
		return $plist;
	} elsif ($plist_obj->isKindOfClass_(NSDictionary->class) ) {
		tie my %plist, 'Mac::Tie::PList::Hash', $plist_obj;
		return \%plist;
	} else {
		carp "Unknown type: $plist_obj\n";
		return;
	}
}


#==============================================================================a#

package Mac::Tie::PList::Hash;

use strict;
use warnings;
use Carp;
use Foundation;
use Tie::Hash;
use base qw(Tie::Hash);

sub TIEHASH {
        my ($class,$plist_obj)  = @_;
        return bless {plist_obj=>$plist_obj, hash=>{} }, $class;
}

sub FETCH {
        my ($obj,$key)  = @_;
	if ($obj->{hash}->{$key}) {
		return $obj->{hash}->{$key};
	} else {
		if ($obj->{plist_obj} && ${$obj->{plist_obj}})  {
        		my $sub_obj  = $obj->{plist_obj}->objectForKey_($key);
        		if ($sub_obj && $$sub_obj ) {
                		return $obj->{hash}->{$key} = Mac::Tie::PList::_tie_plist($sub_obj);
        		} else {
                		return;
        		}
		}
	}
}

sub FIRSTKEY {
        my ($obj)  = @_;
        my @keys;
	if ($obj->{plist_obj} && ${$obj->{plist_obj}})  {
        	my $keys_array = $obj->{plist_obj}->allKeys;
        	for (my $i=0; $i<$keys_array->count; $i++) {
                	my $key_obj = $keys_array->objectAtIndex_($i);
                	if ($key_obj && $$key_obj) { 
                        	push @keys,$key_obj->description->UTF8String;
                	}
        	}
	}

        $obj->{keys} = \@keys;
        return $obj->NEXTKEY;
}

sub NEXTKEY {
        my ($obj)  = @_;
        return shift @{$obj->{keys}};
}


#==============================================================================#

package Mac::Tie::PList::Scalar;

use strict;
use warnings;
use Carp;
use Foundation;
use Tie::Scalar;
use base qw(Tie::Scalar);

sub TIESCALAR {
        my ($class,$plist_obj)  = @_;
        return bless {plist_obj => $plist_obj}, $class;
}

sub FETCH {
        my ($obj)  = @_;

	if ($obj->{plist_obj} && ${$obj->{plist_obj}})  {
		if ($obj->{plist_obj}->isKindOfClass_(NSDate->class) ) {
			return $obj->{plist_obj}->timeIntervalSince1970;
		# TODO Data is in what format? Perl should return a packed string
		#} elsif ($obj->{plist_obj}->isKindOfClass_(NSData->class) ) {
		#	return pack "c*", $obj->{plist_obj}->bytes;
		} else {
			return $obj->{plist_obj}->description->UTF8String;
		}
	} else {
		return;
	}
}


#==============================================================================#

package Mac::Tie::PList::Array;

use strict;
use warnings;
use Carp;
use Foundation;
use Tie::Array;
use base qw(Tie::Array);

sub TIEARRAY {
        my ($class,$plist_obj)  = @_;
        return bless {plist_obj=>$plist_obj, array=>[]}, $class;
}

sub FETCH {
        my ($obj,$n)  = @_;

	if ($obj->{array}->[$n]) {
		return $obj->{array}->[$n];
	} else {
        	my $sub_obj  = $obj->{plist_obj}->objectAtIndex_($n);
        	if ($sub_obj && $$sub_obj ) {
                	return $obj->{array}->[$n] = Mac::Tie::PList::_tie_plist($sub_obj);
        	} else {
                	return;
        	}
	}
}

sub FETCHSIZE {
	my ($obj) = @_;

	if ($obj->{plist_obj} && ${$obj->{plist_obj}})  {
		return $obj->{plist_obj}->count;
        } else {
               	return;
	}
}


#==============================================================================#

=back

=head1 SEE ALSO

This module is based on code from the following O'Reilly article:

	http://www.macdevcenter.com/pub/a/mac/2005/07/29/plist.html

The Objective C Bridge is descibed at:

	http://developer.apple.com/documentation/Darwin/Reference/ManPages/man3/PerlObjCBridge.3pm.html

Further details of NSDictionary's is available here:

	http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_cla
ssic/Classes/NSDictionary.html

=head1 AUTHOR

Gavin Brock, E<lt>gbrock@cpan.orgE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2006 by Gavin Brock

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.0 or,
at your option, any later version of Perl 5 you may have available.

=cut


#
# That's all folks..
#==============================================================================#

1;