#!/usr/bin/perl
# $Id: sync-iPhone-AddressBook.pl,v 1.11 2010/01/12 16:55:41 fukudat Exp $
#
# Copyright (c) 2009, 2010 Takeshi Fukuda.
#
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTY OF ANY KIND.
# You may use, modify, and redistribute this software freely.
#======================================================================

use strict;
use iPhone::AddressBook;

use utf8;
use XML::Simple;
use Net::SCP;
use File::Basename;
use Data::Dumper;    # for debugging

binmode( STDERR, ":utf8" );

#$XML::Simple::PREFERRED_PARSER = 'XML::Parser';
$XML::Simple::PREFERRED_PARSER = 'XML::SAX::PurePerl';

my $VERSION = "0.1";
my $verbose = 1;       # set 1 if you want to see progress messages, etc.

#----------------------------------------------------------------------
# subroutines
#----------------------------------------------------------------------

#　Extracts text content of item
# @param $node	a node in the parsed document
sub text_content {
	my $node = shift;
	my $ref  = ref($node);
	my $buf  = "";

	if ( $ref eq "HASH" ) {
		my $first = 1;
		for my $k ( 'text', 'textlist' ) {
			if ( $node->{$k} && ( my $child = text_content( $node->{$k} ) ) ) {
				$buf .= " " unless $first;
				$buf .= $child;
				undef $first;
			}
		}
	}
	else {
		$buf = $node;
	}
	utf8::decode($buf);
	return $buf;
}

#----------------------------------------------------------------------
# data structure
#----------------------------------------------------------------------

my $phone_key_names = {
	phone_1  => { default => "勤務先",          label => "PhLabel_1", },
	phone_2  => { default => "自宅",             label => "PHLabel_2", },     # "PH" is correct!
	phone_3  => { default => "携帯",             label => "PhLabel_3", },
	phone_4  => { default => "勤務先FAX",       label => "PhLabel_4", },
	phone_5  => { default => "ポケットベル", label => "PhLabel_5", },
	phone_6a => { default => "アシスタント", label => "PhLabel_6a", },    # "6a" is correct!
	phone_7  => { default => "勤務先FAX",       label => "PhLabel_7", },
	phone_8  => { default => "携帯",             label => "PhLabel_8", },
	phone_9  => { default => "勤務先",          label => "PhLabel_9", },
	phone_10 => { default => "自宅",             label => "PhLabel_10", },
};
my @phone_key_names_order =
  ( "phone_1", "phone2", "phone_3", "phone_4", "phone_5", "phone_6a", "phone_7", "phone_8", "phone_9", "phone_10", );

my $phone_key_names_supplemental = {
	PhoneNumber          => "自宅",
	HomeFAXPhoneNumber   => "自宅FAX",
	OfficePhoneNumber    => "勤務先",
	OfficeFAXPhoneNumber => "勤務先FAX",
	CellPhoneNumber      => "携帯",
};
my @phone_key_names_supplemental_order =
  ( "PhoneNumber", "HomeFAXPhoneNumber", "OfficePhoneNumber", "OfficeFAXPhoneNumber", "CellPhoneNumber", );

my $email_key_names = {
	email_1    => { default => "勤務先",          label => "ELabel1" },
	MiscPhone1 => { default => "自宅",             label => "ELabel2" },    # "MiscPhone" is correct!
	MiscPhone2 => { default => "アシスタント", label => "ELabel3" },
	MiscPhone3 => { default => "勤務先",          label => "ELabel4" },
	email_5    => { default => "自宅",             label => "ELabel5" },
};
my @email_key_names_order = ( "email_1", "MiscPhone1", "MiscPhone2", "MiscPhone3", "email_5" );

my $address_entries = {
	Home => {
		key         => "自宅",
		Street      => "StreetAddress",
		City        => "City",
		State       => "State",
		ZIP         => "Zip",
		CountryCode => "country",
	},
	Office => {
		key         => "勤務先",
		Street      => "OfficeStreetAddress",
		City        => "OfficeCity",
		State       => "OfficeState",
		ZIP         => "OfficeZIP",
		CountryCode => "OfficeCountry",
	},
};
my @address_entries_order = ( "Home", "Office" );

#----------------------------------------------------------------------
# main
#----------------------------------------------------------------------

print STDERR basename($0) . " ver. " . $VERSION . "\n" if $verbose;
print STDERR "Copyright (c) 2009, 2010 Takeshi Fukuda.\n" if $verbose;

if ( $#ARGV != 2 ) {
	print STDERR "Usage: $0 AddressBook.xml AddressBook.sqlitedb iPhoneAddress\n";
	exit(1);
}

my $xml_file       = shift @ARGV;
my $db_file        = shift @ARGV;
my $iphone_address = shift @ARGV;

# read XML file...
print STDERR "Loading $xml_file..." if $verbose;
open my $xml, "<:encoding(cp932)", $xml_file || die "FATAL ERROR: cannot open $xml_file";
my $parser = XML::Simple->new( KeyAttr => ['name'] );
my $database = $parser->XMLin($xml);
print STDERR "Completed successfully.\n" if $verbose;

# create DB file...
print STDERR "Populating records in $db_file..." if $verbose;
my $ab = iPhone::AddressBook->new( dbfile => $db_file, init => 1 );

$database->{document} = [ $database->{document} ] if ( ref $database->{document} eq "HASH" );
my $count = 0;
for my $doc ( @{ $database->{document} } ) {
	my $item = $doc->{item};
	print STDERR "." if $verbose && ( ++$count % 10 == 0 );

	my $snJ  = text_content( $item->{LastName} );
	my $gnJ  = text_content( $item->{FirstName} );
	my $kana = text_content( $item->{AltFullNameSort} );
	my $org  = text_content( $item->{CompanyName} );
	my $dept = text_content( $item->{Department} );
	my ( $snK, $gnK ) = split( /[ 　\x3000]+/, $kana );
	my $p = $ab->add_person(
		Last          => $snJ,
		First         => $gnJ,
		LastPhonetic  => $snK,
		FirstPhonetic => $gnK,
		Organization  => $org,
		Department    => $dept
	);

	# phone numbers
	my $no_phone = 1;
	for my $key (@phone_key_names_order) {
		my $value = text_content( $item->{$key} );
		next unless $value;
		my $label_key = $phone_key_names->{$key}->{label};
		my $label = text_content( $item->{$label_key} ) || $phone_key_names->{$key}->{default};

		# warn $key . "[" . $label . "]=" . $value;
		$p->add_phone( $label, $value );
		undef $no_phone;
	}
	if ($no_phone) {
		for my $key (@phone_key_names_supplemental_order) {
			my $value = text_content( $item->{$key} );
			next unless $value;
			my $label = $phone_key_names_supplemental->{$key};

			# warn $key . "[" . $label . "]=" . $value;
			$p->add_phone( $label, $value );
		}
	}

	# email addresses
	for my $key (@email_key_names_order) {
		my $value = text_content( $item->{$key} );
		next unless $value;
		my $label_key = $email_key_names->{$key}->{label};
		my $label = text_content( $item->{$label_key} ) || $email_key_names->{$key}->{default};

		# warn $key . "[" . $label . "]=" . $value;
		$p->add_email( $label, $value );
	}

	# addresses
	for my $key (@address_entries_order) {
		my $map     = $address_entries->{$key};
		my $street  = text_content( $item->{ $map->{Street} } );
		my $city    = text_content( $item->{ $map->{City} } );
		my $state   = text_content( $item->{ $map->{State} } );
		my $zip     = text_content( $item->{ $map->{ZIP} } );
		my $country = text_content( $item->{ $map->{CountryCode} } );
		next unless $street || $city || $state || $zip || $country;
		$country = "ja" unless $country;
		my $label = $map->{key};

		# warn $key . "[" . join( " ", ( $country, $zip, $state, $city, $street ) ) . "]";
		$p->add_address(
			Label   => $label,
			Country => $country,
			ZIP     => $zip,
			State   => $state,
			City    => $city,
			Street  => $street
		);
	}
}
$ab->commit;
print STDERR "Completed successfully ($count records).\n" if $verbose;

# transfer DB file to iPhone
print STDERR "Transferring $db_file to $iphone_address..." if $verbose;
my $scp = Net::SCP->new($iphone_address)
  or die "FATAL ERROR: cannot connect to $iphone_address";
$scp->login("mobile")
  or die "FATAL ERROR: cannot login to $iphone_address as mobile";
$scp->cwd("/private/var/mobile/Library/AddressBook")
  or die "FATAL ERROR: cannot chdir: " . $scp->{errstr};
$scp->put( $db_file, "AddressBook.sqlitedb" )
  or die "FATAL ERROR: cannot copy $db_file: " . $scp->{errstr};
$scp->quit;
print STDERR "Completed successfully.\n" if $verbose;

1;
__END__

