# EAFDSS - Electronic Fiscal Signature Devices Library
#          Ειδική Ασφαλής Φορολογική Διάταξη Σήμανσης (ΕΑΦΔΣΣ)
#
# Copyright (C) 2008 Hasiotis Nikos
#
# ID: $Id: Base.pm 88 2009-04-08 15:21:04Z hasiotis $

package EAFDSS::Base;

=head1 NAME

EAFDSS::Base - EAFDSS Base Class Driver for all other drivers

=head1 DESCRIPTION

Read EAFDSS on how to use the module.

=cut

use 5.6.0;
use strict;
use warnings;
use Carp;
use Class::Base;

use base qw ( Class::Base );

our($VERSION) = '0.60';

=head1 Methods

=head2 init

init

=cut

sub init {
	my($self, $config) = @_;

	if (! exists $config->{DIR}) {
		return $self->error("You need to provide the DIR to save the signnatures!");
	} else {
		$self->{DIR} = $config->{DIR};
	}

	if (! exists $config->{SN}) {
		return $self->error("You need to provide the Serial Number of the device!");
	} else {
		$self->{SN} = $config->{SN};
	}

	return $self;
}

=head2 Sign

Sign

=cut

sub Sign {
        my($self)  = shift @_;
        my($fname) = shift @_;
        my($reply, $totalSigns, $dailySigns, $date, $time, $nextZ, $sign, $fullSign);

        $self->debug("Sign operation");

        if ( ($fname eq '-') || (-e $fname) ) {
		my($replySignDir, $deviceDir) = $self->_createSignDir();
		if ($replySignDir != 0) {
			return $self->error($replySignDir);
		}

		# Slurping the invoice
                open(FH, $fname);
	        my($invoice) = do { local($/); <FH> };
                close(FH);

                $self->debug(  "  Checking file [%s] for invalid characters", $fname);
		my($invalid) = $self->_checkCharacters($invoice);
                if ($invalid)  {
			$self->debug("  File contains invalid characters [%s]", $fname);
			return $self->error(64+0x10);
		}

                $self->debug(  "  Signing file [%s]", $fname);
                ($reply, $totalSigns, $dailySigns, $date, $time, $nextZ, $sign) = $self->PROTO_GetSign($invoice);

		if ($reply == 0) {
			$fullSign = sprintf("%s %04d %08d %s%s %s",
				$sign, $dailySigns, $totalSigns, $self->UTIL_date6ToHost($date), substr($time, 0, 4), $self->{SN});

			$self->_createFileA($invoice, $deviceDir, $date, $dailySigns, $nextZ);
			$self->_createFileB($fullSign, $deviceDir, $date, $dailySigns, $nextZ);

	        	return $fullSign;
		} else {
			return $self->error($reply);
		}
        } else {
                $self->debug(  "  No such file [%s]", $fname);
		return $self->error(64+2);
        }

}

sub _checkCharacters {
        my($self)    = shift @_;
	my($invoice) = shift @_;
	
	my($c);
	foreach $c (unpack('C*', $invoice)) {
		if (grep $_ == ord($c), qw/0 1 2 3 4 5 6 7 8 11 14 15 16 17 18 19 20 21 22 23 24 25 27 28 29 30 31 127 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 173 210 255/ ) {
                	$self->debug("     Found invalid character [%d]", ord($c));
			return 1;
		}
	}

	return 0;
}

=head2 Status

Status

=cut

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

        $self->debug("Status operation");

	my($reply, $status1, $status2, $lastZ, $total, $daily, $signBlock, $remainDaily) = $self->PROTO_ReadSummary();
	if ($reply == 0) {
		my($statusLine) = sprintf("%s %d %d %d %d %d", $self->{SN}, $lastZ, $total, $daily, $signBlock, $remainDaily);
        	return $statusLine;
	} else {
		return $self->error($reply);
	}
}


=head2 GetTime

GetTime

=cut

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

        $self->debug("Read time operation");
	my($reply, $time) = $self->PROTO_ReadTime();
	if ($reply == 0) {
        	return $time;
	} else {
		return $self->error($reply);
	}
}

=head2 SetTime

SetTime

=cut

sub SetTime {
        my($self) = shift @_;
        my($time) = shift @_;

        $self->debug("Set time operation");
	my($reply) = $self->PROTO_SetTime($time);
	if ($reply == 0) {
        	return 0;
	} else {
		return $self->error($reply);
	}
}

=head2 Report

Report

=cut

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

	my($replySignDir, $deviceDir) = $self->_createSignDir();
	if ($replySignDir != 0) {
		return $self->error($replySignDir);
	}

	$self->_validateFilesB();
	$self->_validateFilesC();

        $self->debug("Issue Report operation");

	my($reply1) = $self->PROTO_IssueReport();
	if ($reply1 != 0) {
		return $self->error($reply1);
	}

	my($reply2, $status1, $status2, $totalSigns, $dailySigns, $date, $time, $z, $sn, $closure) = $self->PROTO_ReadClosure(0);
	$self->_createFileC($z, $deviceDir, $date, $time, $closure);

        return $z;
}

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

	my($deviceDir) = sprintf("%s/%s", $self->{DIR}, $self->{SN});

	$self->_validateFilesB();
	$self->_validateFilesC();

        $self->debug("Issue Recovery Report operation");

	my($reply1) = $self->PROTO_IssueReport();
	if ($reply1 != 0) {
		return $self->error($reply1);
	}

	my($reply2, $status1, $status2, $totalSigns, $dailySigns, $date, $time, $z, $sn, $closure) = $self->PROTO_ReadClosure(0);
	$self->_createFileC($z, $deviceDir, $date, $time, $closure);

        return $z;
}

=head2 Info

Info

=cut

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

        $self->debug("Read Info operation");
	my($reply, $version) = $self->PROTO_VersionInfo();
	if ($reply == 0) {
        	return $version;
	} else {
		return $self->error($reply);
	}
}

=head2 Query

Query

=cut

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

        $self->debug("Query for devices");
	my($reply, $devices) = $self->PROTO_Query();
	if ($reply == 0) {
		if ($devices) {
        		return $devices;
		} else {
			return $self->error(64+0x05);
		}
	} else {
		return $self->error($reply);
	}
}

=head2 GetHeaders 

GetHeaders

=cut

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

        $self->debug("Read Headers operation");
	my($reply, @headers) = $self->PROTO_GetHeader();
	if ($reply == 0) {
		return @headers;
	} else {
		return $self->error($reply);
	}
}

=head2 SetHeaders 

SetHeaders

=cut

sub SetHeaders {
        my($self)    = shift @_;
        my($headers) = shift @_;

        $self->debug("Set Headers operation");
	my($reply) = $self->PROTO_SetHeader($headers);
	if ($reply == 0) {
		return 0;
	} else {
		return $self->error($reply);
	}
}

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

	my($result) = $self->_Recover();
	if ($result != 0) {
		return ($result, undef);
	}

	# Create The signs Dir
	if (! -d  $self->{DIR} ) {
		$self->debug("  Creating Base Dir [%s]", $self->{DIR});
		mkdir($self->{DIR});
	}

	my($deviceDir) = sprintf("%s/%s", $self->{DIR}, $self->{SN});
	if (! -d $deviceDir ) {
		$self->debug("  Creating Device Dir [%s]", $deviceDir);
		mkdir($deviceDir);
	}

	return (0, $deviceDir);
}

sub _Recover {
	my($self) = shift @_;
	my($reply, $status1, $status2, $lastZ, $total, $daily, $signBlock, $remainDaily);

	($reply, $status1, $status2) = $self->PROTO_GetStatus();
	if ($reply ne "0") { return $reply };

	my($busy, $fatal, $paper, $cmos, $printer, $user, $fiscal, $battery) = $self->UTIL_devStatus($status1);
	if ($cmos != 1) { return 0 };

	my($day, $signature, $recovery, $fiscalWarn, $dailyFull, $fiscalFull) = $self->UTIL_appStatus($status1);

	$self->debug("   CMOS is set, going for recovery!");

	($reply, $status1, $status2, $lastZ, $total, $daily, $signBlock, $remainDaily) = $self->PROTO_ReadSummary(0);
	if ($reply != 0) {
		$self->debug("   Aborting recovery because of ReadClosure reply [%d]", $reply);
		return $reply
	};

	my($regexA) = sprintf("%s\\d{6}%04d\\d{4}_a.txt", $self->{SN}, $lastZ + 1);
	my($deviceDir) = sprintf("%s/%s", $self->{DIR}, $self->{SN});

	opendir(DIR, $deviceDir) || croak "can't opendir $deviceDir: $!";
	my(@afiles) = grep { /$regexA/ } readdir(DIR);
	closedir(DIR);

	foreach my $curA (@afiles) {
		$self->debug("          Checking [%s]", $curA);
		my($curFileA) = sprintf("%s/%s", $deviceDir, $curA);

		my($curFileB) = $curFileA;
		$curFileB =~ s/_a/_b/;

		my($curB)  = $curA; $curB =~ s/_a/_b/;
		my($curIndex) = substr($curA, 21, 4); $curIndex =~ s/^0*//;

		$self->debug("            Resigning file A [%s]", $curA);
		open(FH, $curFileA);

		my($reply, $totalSigns, $dailySigns, $date, $time, $nextZ, $sign) = $self->PROTO_GetSign(*FH);
		my($fullSign) = sprintf("%s %04d %08d %s%s %s", $sign, $dailySigns, $totalSigns, $self->UTIL_date6ToHost($date), substr($time, 0, 4), $self->{SN});
		close(FH);

		$self->debug("            Updating file  B [%s] -- Index [%d]", $curB, $curIndex);
		open(FB, ">>", $curFileB) || croak "Error: $!";
		print(FB "\n" . $fullSign); 
		close(FB);
	}

	my($z) = $self->_RecoveryReport();
	if ($z) {
		return(0);
	} else {
		my($errNo) = $self->error();
		return $self->error($errNo);
	}

}

sub _createFileA {
	my($self)    = shift @_;
	my($invoice) = shift @_;
	my($dir)     = shift @_;
	my($date)    = shift @_;
	my($ds)      = shift @_;
	my($curZ)    = shift @_;

	my($fnA) = sprintf("%s/%s%s%04d%04d_a.txt", $dir, $self->{SN}, $self->UTIL_date6ToHost($date), $curZ, $ds);
	$self->debug("   Creating File A [%s]", $fnA);
	open(FA, ">", $fnA) || croak "Error: $!";
	print(FA $invoice);
	close(FA);
}

sub _createFileB {
	my($self) = shift @_;
	my($fullSign)   = shift @_;
	my($dir)  = shift @_;
	my($date) = shift @_;
	my($ds)   = shift @_;
	my($curZ) = shift @_;

	my($fnB) = sprintf("%s/%s%s%04d%04d_b.txt", $dir, $self->{SN}, $self->UTIL_date6ToHost($date), $curZ, $ds);
	$self->debug("   Creating File B [%s]", $fnB);
	open(FB, ">", $fnB) || croak "Error: $!";
	print(FB $fullSign);
	close(FB);
}

sub _createFileC {
        my($self) = shift @_;
        my($z)    = shift @_;
        my($dir)  = shift @_;
        my($date) = shift @_;
        my($time) = shift @_;
        my($closure) = shift @_;

        my($fnC) = sprintf("%s/%s%s%s%04d_c.txt", $dir, $self->{SN}, $date, $self->UTIL_time6toHost($time), $closure);
        $self->debug(  "   Creating File C [%s]", $fnC);

        open(FC, ">", $fnC) || croak "Error: $!";
        print(FC $z); 
        close(FC);
}


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

        my($reply, $status1, $status2, $lastZ, $total, $daily, $signBlock, $remainDaily) = $self->PROTO_ReadSummary();
        if ($reply != 0) { return $reply};

        my($regexA) = sprintf("%s\\d{6}%04d\\d{4}_a.txt", $self->{SN}, $lastZ + 1);
        $self->debug(  "    Validating B Files for #%d Z with regex [%s]", $lastZ + 1 , $regexA);
        my($deviceDir) = sprintf("%s/%s", $self->{DIR}, $self->{SN});

        opendir(DIR, $deviceDir) || croak "can't opendir $deviceDir: $!";
        my(@afiles) = grep { /$regexA/ } readdir(DIR);
        closedir(DIR);

        foreach my $curA (@afiles) {
                $self->debug(  "          Checking [%s]", $curA);
                my($curFileA) = sprintf("%s/%s", $deviceDir, $curA);

                my($curFileB) = $curFileA;
                $curFileB =~ s/_a/_b/;

                if (! -e $curFileB) { # TODO: Add size Check
                        my($curB)  = $curA; $curB =~ s/_a/_b/;
                        my($curIndex) = substr($curA, 21, 4); $curIndex =~ s/^0*//;
                        $self->debug(  "            Recreating file B [%s] -- Index [%d]", $curB, $curIndex);

                        my($replyCode, $status1, $status2, $totalSigns, $dailySigns, $date, $time, $sign, $sn, $closure) = $self->PROTO_ReadSignEntry($curIndex);
                        my($fullSign) = sprintf("%s %04d %08d %s%s %s", $sign, $dailySigns, $totalSigns, $self->UTIL_date6ToHost($date), substr($time, 0, 4), $self->{SN});

                        open(FB, ">",  $curFileB) || croak "Error: $!";
                        print(FB $fullSign); 
                        close(FB);
                }
        }

        return;
}

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

        my($reply, $status1, $status2, $lastZ, $total, $daily, $signBlock, $remainDaily) = $self->PROTO_ReadSummary();
        if ($reply != 0) { return $reply };

        my($curClosure, $curFileC, $matched);

        my($regexC) = sprintf("%s.*_c.txt", $self->{SN}, $lastZ + 1);
        $self->debug(  "    Validating C Files for, total of [%d]", $lastZ);
        my($deviceDir) = sprintf("%s/%s", $self->{DIR}, $self->{SN});

        opendir(DIR, $deviceDir) || croak "can't opendir $deviceDir: $!";
        my(@cfiles) = grep { /$regexC/ } readdir(DIR);
        closedir(DIR);

        for ($curClosure = 1; $curClosure <= $lastZ;  $curClosure++) {
                $self->debug(  "      Searching for [%d]", $curClosure);

                $matched = 0;
                foreach (@cfiles) {
                        if (/${curClosure}_c\.txt$/) { 
                                $curFileC = $_;
                                $matched = 1;
                                last;
                        }
                }

                if ($matched) { 
                        $self->debug(  "          Keeping file C    [%s] -- Index [%d]", $curFileC, $curClosure);
                } else {
                        my($replyCode, $status1, $status2, $totalSigns, $dailySigns, $date, $time, $z, $sn, $closure) = $self->PROTO_ReadClosure($curClosure);
                        my($fnC) = sprintf("%s%s%s%04d_c.txt", $sn, $date, $time, $curClosure);
                        $self->debug(  "          Recreating file C [%s] -- Index [%d]", $fnC, $curClosure);

                        open(FC, ">", $deviceDir . "/" . $fnC) || croak "Error: $!";
                        print(FC $z); 
                        close(FC);
                }
        }
}


sub DESTROY {
        my($self) = shift;
        #printfv("Destroying %s %s",  $self, $self->name );
}

=head2 debug

debug

=cut

sub debug {
	my($self)  = shift;
	my($flag);

	if (ref $self && defined $self->{ _DEBUG }) {
		$flag = $self->{ _DEBUG };
	} else {
		# go looking for package variable
		no strict 'refs';
		$self = ref $self || $self;
		$flag = ${"$self\::DEBUG"};
	}

	return unless $flag;

	printf(STDERR "[%s] %s\n", $self->id, sprintf(shift @_, @_));
}


# Preloaded methods go here.

1;
__END__


=head1 VERSION

This is version 0.60.

=head1 AUTHOR

Hasiotis Nikos, E<lt>hasiotis@gmail.comE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2008 by Hasiotis Nikos

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

=cut
