###########################################################################
#
# ExpediteeFrameIO.pm -- 
# A component of the Greenstone digital library software
# from the New Zealand Digital Library Project at the 
# University of Waikato, New Zealand.
#
# Copyright (C) 2009 New Zealand Digital Library Project
#
# This program is free software; you can redistr   te it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
###########################################################################

package ExpediteeFrameIO;

use strict;

use CssStyleToExpAttr;

sub new 
{
    my $class = shift(@_);
    my $output_dir = shift(@_);
    my $username   = shift(@_) || "greenstone";

    my $self = { 'items' => [], 'lines' => [], 'constraints' => [] };

    $self->{'output_dir'} = $output_dir;
    $self->{'username'} = $username;

    return bless $self, $class;
}

sub getFormattedDate
{
    my ($opt_mode) = @_;

    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);

    my @mabbr = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );

    $year += 1900;

    my $fdate;

    if ((defined $opt_mode) && ($opt_mode eq "highPrecision")) {
	$fdate = sprintf("%02d%s%04d[%02d:%0d2.%02d]", 
			 $mday, $mabbr[$mon],$year,$hour,$min,$sec);
    }
    else {
	$fdate = sprintf("%02d%s%04d[%02d:%0d2]", 
			 $mday, $mabbr[$mon],$year,$hour,$min);
    }

    return $fdate;
}

sub convertStyleToAttr
{
    my ($css_attr,$compute_font) = @_;

    my $exp_attr = {};

    # load up some defaults for font information
    my $exp_font_family = "s"; # t
    my $exp_font_face = "r";
    my $exp_font_size = "18";

	if(defined $compute_font){
		if($compute_font eq "true"){
	
			if (defined $css_attr->{'font-size'}) {
		
				my $css_font_size = $css_attr->{'font-size'};
				my $new_exp_font_size = CssStyleToExpAttr::convert_font_size($css_font_size);

				if(defined $new_exp_font_size){
					$exp_font_size = $new_exp_font_size;
				}
		}
	
		if(defined $css_attr->{'font-family'}){
			my $obtain_font_family = $css_attr->{'font-family'};
			my $new_exp_font_family = CssStyleToExpAttr::convert_font_family($obtain_font_family);	

			if(defined $new_exp_font_family){
				$exp_font_family = $new_exp_font_family;
			}
		}
	
		#obtain font style/weight - bold, italic or bold-italic.
	
		if(defined $css_attr->{'font-style'}){
			my $css_font_style = $css_attr->{'font-style'};
			my $new_exp_font_face = CssStyleToExpAttr::convert_font_face($css_font_style);
		
			if(defined $new_exp_font_face){
				$exp_font_face = $new_exp_font_face;
			}
		}
	
		if(defined $css_attr->{'font-weight'}){	
			my $css_font_face = $css_attr->{'font-weight'};
			my $new_exp_font_face = CssStyleToExpAttr::convert_font_face($css_font_face);
		
			if(defined $new_exp_font_face){
				$exp_font_face = $new_exp_font_face;
			}
		}
	
		if(defined $css_attr->{'font-style'} && defined $css_attr->{'font-weight'}){
			my $font_face_param = $css_attr->{'font-weight'}.'-'.$css_attr->{'font-style'};
			my $new_exp_font_face = CssStyleToExpAttr::convert_font_face($font_face_param);
		
			if(defined $new_exp_font_face){
				$exp_font_face = $new_exp_font_face;
			}
		}
	
		# color 
		if(defined $css_attr->{'color'}){
			my $css_color = $css_attr->{'color'};
		
			my $exp_color = CssStyleToExpAttr::convert_color($css_color);
		
			$exp_attr->{'d'} = $exp_color;
		}
		}
	}
	
    $exp_attr->{'f'} = $exp_font_family.$exp_font_face.$exp_font_size;

    # background color
    if (defined $css_attr->{'background-color'}) {
		my $css_color = $css_attr->{'background-color'};

		my $exp_color = CssStyleToExpAttr::convert_color($css_color);

		$exp_attr->{'e'} = $exp_color;
    }
	

	
    return $exp_attr;
}


sub _nextFreeId
{
    my $self = shift @_;

    my $items = $self->{'items'};
    my $lines = $self->{'lines'};
    my $constraints = $self->{'constraints'};
    
    # Ids start at base of 1
    return 1+(scalar(@$items) + scalar(@$lines) + scalar(@$constraints));
}


sub _addItem
{
    my $self = shift @_;
    my ($type,$attr) = @_;

    # By this point 'attr' is synonymous with being an item

    my $items = $self->{'items'};

    my $next_free_id = $self->_nextFreeId();

    $attr->{'_type'} = $type;
    $attr->{'_id'} = $next_free_id;

    push(@$items,$attr);

    return ($attr,$next_free_id);
}



sub _setBaseDefaultAttributes
{
    my $self = shift @_;
    my ($attr) = @_;

    $attr->{'o'} = $self->{'username'};
    $attr->{'s'} = getFormattedDate("highPrecision");
    $attr->{'Q'} = "0";     # gradient
    $attr->{'v'} = "S";     # dot type
}


sub setPointDefaultAttributes
{
    my $self = shift @_;
    my ($attr) = @_;

    $self->_setBaseDefaultAttributes($attr);
}

sub setTextDefaultAttributes
{
    my $self = shift @_;
    my ($attr) = @_;

    $self->_setBaseDefaultAttributes($attr);

    if(defined $attr->{'d'}){

    }
    else {
	$attr->{'d'} = "0 0 0"; # black color
    }

}


sub setRectPointDefaultAttributes
{
    my $self = shift @_;
    my ($attr) = @_;


    $self->setPointDefaultAttributes($attr);

    if((defined $attr->{'d'}) && (defined $attr->{'h'})){

    }
    else {
	    $attr->{'d'} = "80 80 80"; # grey color for rect lines
	    $attr->{'h'} = "1.0";     # line thickness
    }
}


sub addRectPoint
{
    my $self = shift @_;
    my ($x, $y, $attr) = @_;

    my %attr_copy = %$attr; # make a private copy of 'attr'

    $self->setRectPointDefaultAttributes(\%attr_copy);

    my $items = $self->{'items'};

    $attr_copy{'P'} = "$x $y";

    return $self->_addItem("P",\%attr_copy);
}

sub addText
{
    my $self = shift @_;
	my ($x,$y,$text,$w,$attr) = @_;
	
	my %attr_copy = %$attr; #make a private copy of 'attr'
	
	$self->setTextDefaultAttributes(\%attr_copy);
	my $items = $self->{'items'};
	
	$attr_copy{'P'} = "$x $y";
	$attr_copy{'T'} = $text;
	#$attr_copy{'w'} = "-$w" if (defined $w);
	$attr_copy{'w'} = "$w" if (defined $w);
	
	return $self->_addItem("T",\%attr_copy);
}

sub addLine
{
    my $self = shift @_;

    my ($item_id1,$item_id2) = @_;

    my $lines = $self->{'lines'};
    my $line_type = 1;

    my $next_free_id = $self->_nextFreeId();

    my $attr = { 'L' => "$next_free_id $line_type" };

    $attr->{'s'} = "$item_id1 $item_id2";

    push(@$lines,$attr);

    return ($attr,$next_free_id);
}


sub addConstraint
{
    my $self = shift @_;

    my ($orientation,$item_id1,$item_id2) = @_;

    my $constraints = $self->{'constraints'};

    my $orientation_type = undef;
    if ($orientation eq "vertical") {
	$orientation_type = 2;
    }
    else {
	# assume horizontal for now
	$orientation_type = 3;
    }

    my $next_free_id = $self->_nextFreeId();

    my $attr = { 'C' => "$next_free_id $orientation_type" };

    $attr->{'s'} = "$item_id1 $item_id2";

    push(@$constraints,$attr);

    return ($attr,$next_free_id);
}


sub addRect
{
    my $self = shift @_;

    my ($xl, $yt, $xr, $yb, $attr) = @_;
    
    # do point in same order Expeditee puts them in
    my ($p_tr,$p_tr_id) = $self->addRectPoint($xr,$yt,$attr);
    my ($p_tl,$p_tl_id) = $self->addRectPoint($xl,$yt,$attr);
    my ($p_bl,$p_bl_id) = $self->addRectPoint($xl,$yb,$attr);
    my ($p_br,$p_br_id) = $self->addRectPoint($xr,$yb,$attr);

    my ($l_t,$l_t_id) = $self->addLine($p_tr_id,$p_tl_id);
    my ($l_l,$l_l_id) = $self->addLine($p_tl_id,$p_bl_id);
    my ($l_b,$l_b_id) = $self->addLine($p_bl_id,$p_br_id);
    my ($l_r,$l_r_id) = $self->addLine($p_br_id,$p_tr_id);

    my ($c_t,$c_t_id) = $self->addConstraint("horizontal",$p_tr_id,$p_tl_id);
    my ($c_l,$c_l_id) = $self->addConstraint("vertical"  ,$p_tl_id,$p_bl_id);
    my ($c_b,$c_b_id) = $self->addConstraint("horizontal",$p_bl_id,$p_br_id);
    my ($c_r,$c_r_id) = $self->addConstraint("vertical"  ,$p_br_id,$p_tr_id);

    $p_tr->{'l'} = "$l_t_id $l_r_id";
    $p_tl->{'l'} = "$l_t_id $l_l_id";
    $p_bl->{'l'} = "$l_l_id $l_b_id";
    $p_br->{'l'} = "$l_b_id $l_r_id";

    $p_tr->{'c'} = "$c_t_id $c_r_id";
    $p_tl->{'c'} = "$c_t_id $c_l_id";
    $p_bl->{'c'} = "$c_l_id $c_b_id";
    $p_br->{'c'} = "$c_b_id $c_r_id";

}

sub writeHeaderSection
{
    my $self = shift @_;

    # Example header:
    #   V 1
    #   p 4
    #   U davidb
    #   D 09Jan2012[13:33]
    #   M davidb
    #   d 09Jan2012[13:33]
    #   Z
    #   

    # Legend:
    #   V = version
    #   p = permision level
    #   U = username (owner)
    #   M = last modified by
    #   D, d = date information
    #   Z => end of section 


    my $username = $self->{'username'};

    my $fdate = getFormattedDate();

    print FOUT "V 1\n";
    print FOUT "p 4\n";
    print FOUT "U $username\n";
    print FOUT "D $fdate\n";
    print FOUT "M $username\n";
    print FOUT "d $fdate\n";
    print FOUT "Z\n\n";

}


sub writeItemsSection
{
    my $self = shift @_;

    my $items = $self->{'items'};

    foreach my $item (@$items) {

	my $type = delete $item->{'_type'};
	my $id = delete $item->{'_id'};

	if(defined($type) && defined($id)) {

	     print FOUT "S $type $id\n";

	     foreach my $a (keys %$item) {
	         print FOUT "$a ", $item->{$a}, "\n";
	     }
	
     	     print FOUT "\n";

	  }

     }

    print FOUT "Z\n\n";
}

sub writeLinesSection
{
    my $self = shift @_;

    my $lines = $self->{'lines'};

    foreach my $line (@$lines) {

	print FOUT "L ", $line->{'L'}, "\n";
	print FOUT "s ", $line->{'s'}, "\n";
	
	print FOUT "\n";
    }

    print FOUT "Z\n\n";

}

sub writeConstraintsSection
{
    my $self = shift @_;

    my $constraints = $self->{'constraints'};

    foreach my $constraint (@$constraints) {
	print FOUT "C ", $constraint->{'C'}, "\n";
	print FOUT "s ", $constraint->{'s'}, "\n";
	
	print FOUT "\n";
    }

    print FOUT "Z\n\n";
}

sub writeStatisticsSection
{
    my $self = shift @_;

    # Currently do nothing
}

sub saveZeroFrame
{
    my $self = shift @_;
    my $file = "0.exp";

    my $filename = &util::filename_cat($self->{'output_dir'},$file);

    my $status = undef;

	my $username = $self->{'username'};
    my $fdate = getFormattedDate();

    if (open(FOUT,">$filename")) {
	binmode(FOUT,":utf8");
	
	print FOUT <<EOT;
	
V 1
p 4
U $username
D $fdate
M $username
d $fdate
Z

Z

Z

Z
	
EOT

	close(FOUT);
	$status = 1;
    }
    else {
	print STDERR "ExpediteeFrameIO::saveZeroFrame() Failed to open $filename for output\n";
	$status = 0;
    }

    return $status;
}

sub writeAssocFilePath
{
	my $self = shift @_;
	my ($assoc) = @_;
	
	my $x = 318;
	my $y = 123;
	my $text = "\@assocfilepath: $assoc";
	
	my $attr = {};
	
	#add data: gsdl.Metadata: assocfilepath to this piece of text.
	$attr->{'D'} = "gsdl.Metadata: assocfilepath";
	
	$self->addText($x,$y,$text,undef,$attr);
}

sub saveFrame
{
    my $self = shift @_;
    my ($file,$assoc) = @_;

	if ($file eq "1.exp") {
		$self->saveZeroFrame();
	}

    my $filename = &util::filename_cat($self->{'output_dir'},$file);

    my $status = undef;

    if (open(FOUT,">$filename")) {
	binmode(FOUT,":utf8");
	
	if(defined $assoc){
		$self->writeAssocFilePath($assoc);		#write assocfilepath out to frame.
	}
	
	$self->writeHeaderSection();
	$self->writeItemsSection();
	$self->writeLinesSection();
	$self->writeConstraintsSection();
	$self->writeStatisticsSection();
	
	close(FOUT);
	$status = 1;
    }
    else {
	print STDERR "ExpediteeFrameIO::saveFrame() Failed to open $filename for output\n";
	$status = 0;
    }

    return $status;
}

sub buildFrame
{
    my $self = shift @_;
    my ($html_node,$compute_font) = @_;
 
	my $type = $html_node->{'type'};

    if ($type eq "rect") {

	my $rect = $html_node->{'rect'};
	my $xl = $rect->{'xl'};
	my $xr = $rect->{'xr'};
	my $yt = $rect->{'yt'};
	my $yb = $rect->{'yb'};
	
	my $attr = convertStyleToAttr($html_node->{'style'});

	if (defined $html_node->{'attr'}) {
	    # values provided in 'attr' explicitly overwrite any values 
	    # derived from CSS style

	    my $direct_attr_str = $html_node->{'attr'};
	    my @direct_attr_array = split(/\s*;\s*/,$direct_attr_str);
	    foreach my $da (@direct_attr_array) {
		my ($key,$val) = ($da =~ m/^(.)\s*(.*)$/);
		$attr->{$key} = $val;
	    }
	}
	
	#don't want to add font information to non-text items!
	my $deleted = delete $attr->{'f'};

	$self->addRect($xl,$yt,$xr,$yb,$attr);

	if (defined $html_node->{'img'}) {

	    my $img_url = $html_node->{'img'};
	    $img_url =~ s/^http:\/\/(.*?)\/greenstone3(.*?)\///;
	    if ($img_url =~ m/^interfaces\//) {
		$img_url = "greenstone3-svn/web/$img_url";
	    }
	    elsif ($img_url =~ m/^sites\//) {
#		if ($img_url =~ m/^sites\//) {
#		$img_url =~ s/^sites\/(.*?)\//images\//;
			$img_url = "greenstone3-svn/web/$img_url";
	    }

	    my $x = $xl;
	    my $y = $yt;

	    my $attr = {};
		
		my $link = $html_node->{'link'};
		$attr->{'F'} = $link if defined $link;
		
	    my $img_text = "\@i: $img_url";

	    $self->addText($x,$y,$img_text,undef,$attr);
	}

    }
    elsif ($type eq "text") {
		
	my $text = $html_node->{'text'};

	my $x = $html_node->{'xl'};
	my $y = $html_node->{'yt'};
	#my $w = $html_node->{'xr'} - $x +1;
	
	my $w = undef;
	
	if(defined $html_node->{'width'}){
		$w = $html_node->{'width'} - 5;		#take away 5px for extra space.
	}
	
	my $attr = convertStyleToAttr($html_node->{'style'},$compute_font);
	
	# fudge factor for now (based on default font size used)
	$y += 16; # y-value of text item in Expeditee is it's base line
	$x += 4;
	
	my $link = $html_node->{'link'};
	$attr->{'F'} = $link if defined $link;

	my $data = $html_node->{'data'};
	$attr->{'D'} = $data if defined $data;
 
	$self->addText($x,$y,$text,$w,$attr);
    }
    else {
	print STDERR "ExpediteeFrameIO::buildFrame(): Warning, unrecognized type '$type'\n";
    }

    my $childNodes = $html_node->{'childNodes'};
    foreach my $child_node (@$childNodes) {
	$self->buildFrame($child_node,$compute_font);
    }
}


sub saveLastFrameNumber
{
    my $self = shift @_;
    my ($last_frame_number,$collect) = @_;

    my $filename = &util::filename_cat($self->{'output_dir'},"frame.inf");

    my $status = undef;

    if (open(FNOUT,">$filename")) {
	binmode(FNOUT,":utf8");

	#writes frameset name concatenated with last frame number in the set to the frame.inf file.	
     #   my $getFramesetName = $self->{'output_dir'};
	
	#use collection name rather than the directory name where the frameset is stored, when saving the last frame name/number to the frame.inf file.
	print FNOUT "$collect"."$last_frame_number";
	
	close(FNOUT);
	$status = 1;
    }
    else {
	print STDERR "ExpediteeFrameIO::saveLastFrameNumber() Failed to open $filename for output\n";
	
	$status = 0;
    }

    return $status;

}

1;
