package RPS::Sale::File::EMI::YouTubeMonthly;

use strict;
use Data::Dumper;

#use warnings;

use Devel::Peek;
use Encode;

use Spreadsheet::ParseExcel;
use Spreadsheet::XLSX;

use Text::CSV_XS;
use Date::Calc qw(Add_Delta_YMD Add_Delta_Days);

# built in perl func
use File::Copy;

use lib '/app/tools/sale_import/lib';
use lib '/app/tools/rps/lib';
use RPS::Import::Service;

use lib '/app/tools/common/lib';
use Common::Util;
use Common::Log;
use Common::File::UTF16;
use Common::File::UTF8;
use Common::File::Latin1;
use Common::UTF8;
use Spreadsheet::XLSX;

use Common::File::Tied::Parsed;

use lib '/app/tools/data_classes/lib';
use RPS::File::File;
use Client::Service;
use Client::Client;

use base 'RPS::Sale::File';

sub _getSheetIterators {
    my ( $self, $path ) = @_;

    my $sheets;
    my $sheetNames;

    my $fileExtension;
    if ( $path =~ m/(\.[a-z]+)$/ ) {
        $fileExtension = $1;
    }

    if ( -B $path && $fileExtension =~ /.xlsx?/ ) {
        die "I have no intention of supporting Excel files at this time...";

        # !!! But if I did, it would look something like this...
        #
        Common::Log::Print("ParseFull: reading excel file");

        my $sheet_data = $self->_read_excel_file() || return undef;

        $sheets     = $sheet_data->[0];
        $sheetNames = $sheet_data->[1];

        unless ( ref( $sheets->[0] ) eq 'ARRAY' ) {
            $self->errstr('Failed to read excel file');
            return undef;
        }
    } else {
        $sheets = [];
        my $file;
        if ( Common::File::UTF8->IsValid($path) ) {
            $file = Common::File::UTF8->new($path);
        } elsif ( Common::File::UTF16->IsValid($path) ) {
            $file = Common::File::UTF16->new($path);
        } elsif ( ( -T $path && $fileExtension ne '.pdf' ) || $fileExtension eq '.csv' ) {

            # !!! Note that I'm not sure we can rely on the stuff in a 'latin1' file to
            # !!! actually _be_ latin1.    We may have to inline calls to Common::UTF8::Encode in that class.
            #
            $file = Common::File::Latin1->new($path);
        } else {
            $self->errstr('File not identified');
            return;
        }

        $sheets->[0] = $self->_rowArrayRefFromFile($file);
    }

    return ( $sheets, $sheetNames );
}

sub _rowArrayRefFromFile {
    my ( $self, $file ) = @_;

    # So, I have a file object.  That's good!
    # I'll need to wrap a class around it that provides the right interface.
    #

    my @fileLines;
    tie @fileLines, 'Common::File::Tied::Parsed', $file;

    return \@fileLines;
}

sub ParseFull {
    my $self = shift;
    my %args = @_;

    $self->_init();

    my $client_id = $args{client_id};
    my $file_obj  = $args{file};

    if ( !$client_id ) {
        $self->errstr("Client ID not specified");
        return undef;
    }

    if ( !$file_obj ) {
        $self->errstr("File object not provided");
        return undef;
    }

    my $path_to_import_file = $args{file_path} || $file_obj->FileDir . "/" . $file_obj->FileName;
    $self->{path} = $path_to_import_file;

    if ( !-e $path_to_import_file ) {
        $self->errstr("File $path_to_import_file does not exist");
        return undef;
    }

    Common::Log::Print("ParseFull: reading file: $path_to_import_file");

    # JPK - Going to change the model here.
    # What we're going to do is identify the file, then instantiate
    # a 'Row Iterator' object for each 'sheet'.  For now, since we're just
    # working on text files, there will be only one sheet.
    #
    # In order to support all the legacy importers, this iterator will support
    # a tied array reference interface.    I'll figure out the details later...
    #
    # It's pretty dumb that we need to support both this 'sheets_aref' and 'aref_lines_array'.
    # The new factory method that returns the row iterators will return essentially the sheets_aref.
    #
    my ( $sheets_aref, $sheetnames_aref ) = $self->_getSheetIterators($path_to_import_file);
    my $aref_lines_array = $sheets_aref->[0];

    Common::Log::Print("ParseFull: determining service/version");
    if ( $args{service_id} && $args{version} ) {
        print STDERR "ParseFull: forcing to service $args{service_id} : $args{version}\n";
        $self->{service_id}  = $args{service_id};
        $self->{version_num} = $args{version};
    } elsif ( $self->PreParse( $sheets_aref, $file_obj->OrigFileName ) ) {
        print STDERR "ParseFull: PreParse determined file service is $self->{service_id}, version $self->{version_num}\n";
    } else {
        $self->errstr('File not identified');
        return undef;
    }

    Client::Service::AddService( client_id => $client_id, service_id => $self->{service_id} );
    $file_obj->ServiceID( $self->{service_id} );
    $file_obj->VersionNum( $self->{version_num} );
    $file_obj->TypeID( $self->{type_id} );

    ## indicate if it's a physical sales file
    ## clean this later... maybe some flag in the Client::Service
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_NAVARRE );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_FAITHWORKS );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_ECHO );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_DTTHIRDPARTY );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_DTARTISTDIRECT );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_DTINTERNATIONAL );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_BMG );

    #$file_obj->Physical(1) if ($self->{service_id} == Client::Service::DSP_RYKODISC);
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_DOCK );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_FUSION );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_ROOTSY );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_ROUGHTRADEDIST );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_RED );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_KOCHENTCANADA );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_HARMONIAMUNDI );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_FONTANA );

    #$file_obj->Physical(1) if ($self->{service_id} == Client::Service::DSP_PROPERUK);
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_KOCH_PHYSICAL );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_LOOKOUTDIRECT );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_LMMGSALES );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_PIAS_PHYSICAL );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_BMG_COLUMBIAHOUSE );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_SANCT_HISTORICAL );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_ADAPHYS );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_BMG_SONY );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_SHELLSHOCK );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_CAROLINE );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_DOWNLOADCENTRIC );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_WELK );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_OUTSIDEMUSIC );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_SDROUTSIDESALES );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_TOUCHNGOPHYS );
    $file_obj->Physical(1) if ( $self->{service_id} == Client::Service::DSP_NAXOS_PHYSICAL );

    $file_obj->Save();
    sleep(4);

    print STDERR "calling importer...\n";

    my %constructionArgs = (
        service_id  => $file_obj->ServiceID,
        version_num => $file_obj->VersionNum,
    );
    if ( $args{sale_object_class} ) {
        $constructionArgs{sale_object_class} = $args{sale_object_class};
    }

    my $importer = RPS::Import::Service->new(%constructionArgs);

    if ( $importer && $importer->physical ) {
        $file_obj->Physical(1);
        $file_obj->Save();
    }

    # if we parsed an excel file then
    # we will have an arrayref or arrayrefs (ie. sheets)
    # so pass those as a 'sheets' param in leui of a 'lines' param

    my %importArgs = (
        client_id   => $client_id,
        file        => $file_obj,
        lines       => $aref_lines_array,
        sheets      => $sheets_aref,
        sheet_names => $sheetnames_aref,
    );

    my $result = $importer->Import(%importArgs);

    $self->errstr( $importer->errstr() );
    return $result;
}

sub PreParse {
    my $self      = shift;
    my $sheets    = shift;
    my $file_name = shift;

    $self->_init();

    my $rules = $self->_file_match_rules();

    my $service_id_found  = 0;
    my $version_num_found = 0;
  RULE:
    foreach my $rule (@$rules) {
        my $service_id  = $rule->{service};
        my $version_num = $rule->{version};
        my $row_limit   = $rule->{limit} || 80;

        Common::Log::Debug("======== SERVICE: $service_id, VERSION: $version_num ===========================");

        if ( $rule->{file_name} ) {
            my $fname_match = $rule->{file_name};
            if ( $file_name !~ m/$fname_match/i ) {
                next RULE;
            }
        }

        # default to the first sheet
        my $data_sheet_index = $rule->{sheet} || 0;
        my $i = 0;
      SHEET:
        foreach my $sheet (@$sheets) {
            if ( ref($sheet) ne 'ARRAY' or ( $data_sheet_index ne 'any' and $data_sheet_index != $i ) ) {
                $i++;
                next SHEET;
            }

            my $data = $rule->{lines};

            if ( $rule->{match_on_any_row} ) {

                # look for header match on any of the first $row_limit rows
                my $limit = scalar @$sheet;
                $limit = $row_limit if ( $limit > $row_limit );

              OFFSET:
                for ( my $offset = 0 ; $offset < $limit ; $offset++ ) {
                    for ( my $row = 0 ; $row < scalar @$data ; $row++ ) {
                        for ( my $col = 0 ; $col < scalar @{ $data->[$row] } ; $col++ ) {
                            Common::Log::Debug(
                                "HEADER: row(" . ( $row + $offset ) . ") col($col) = '" . $sheet->[ $row + $offset ][$col] . "'" );
                            if ( defined $data->[$row][$col] ) {
                                Common::Log::Debug("HEADER: ($sheet->[$row+$offset][$col]), DATA: ($data->[$row][$col])");
                                if ( $sheet->[ $row + $offset ][$col] =~ m/$data->[$row][$col]/i ) {
                                    $service_id_found  = $service_id;
                                    $version_num_found = $version_num;
                                } else {
                                    $service_id_found  = 0;
                                    $version_num_found = 0;
                                    next OFFSET;
                                }
                            }
                        }
                    }
                    last OFFSET if ($service_id_found);
                }
            } else {
                for ( my $row = 0 ; $row < scalar @$data ; $row++ ) {
                    for ( my $col = 0 ; $col < scalar @{ $data->[$row] } ; $col++ ) {
                        Common::Log::Debug( "HEADER: row($row) col($col) = '" . $sheet->[$row][$col] . "'" );
                        if ( defined $data->[$row][$col] ) {
                            Common::Log::Debug("HEADER: ($sheet->[$row][$col]), DATA: ($data->[$row][$col])");
                            if ( $sheet->[$row][$col] =~ m/$data->[$row][$col]/i ) {
                                $service_id_found  = $service_id;
                                $version_num_found = $version_num;
                            } else {
                                $service_id_found  = 0;
                                $version_num_found = 0;
                                $i++;
                                next SHEET;
                            }
                        }
                    }
                }
            }

            if ($service_id_found) {
                $self->{service_id}  = $service_id_found;
                $self->{version_num} = $version_num_found;
                return $service_id_found;
            }
            $i++;
        }
    }

    $self->errstr("Unrecognized file format");
    return undef;
}

sub _read_utf16_file {
    my $self      = shift;
    my %args      = @_;
    my $utf16File = $args{file};
    my $lines     = $utf16File->read();

    $self->{delimiter} = Common::Util::GetDelimiter( join( "\n", @$lines ) );

    if ( $self->{delimiter} ) {
        return $self->_read_text_file( %args, lines => $lines );
    } else {
        $self->errstr('Can not determine delimeter');
        return ();
    }
}

# JPK - All this knowledge concerning how to parse individual lines will end up abstracted
# away into the 'Common::File::Tied::LineParser' class.
#
sub _read_text_file {
    my $self = shift;
    my %args = @_;

    $self->{file_type} = 't';

    my $csv = Text::CSV_XS->new( { binary => 1 } );    # actually means to allow characters outside of non-ASCII range
    $csv = Text::CSV_XS->new( { binary => 1, escape_char => "\\" } ) if ( $args{clean_quotes} == 1 && $self->{possible_quote} == 1 );
    my $alternateCSV = Text::CSV_XS->new( { allow_loose_quotes => 1, escape_char => '' } );

    my $semicolonCSV = Text::CSV_XS->new( { binary => 1, sep_char => ';' } );
    $semicolonCSV = Text::CSV_XS->new( { binary => 1, sep_char => ';', escape_char => "\\" } )
      if ( $args{clean_quotes} == 1 && $self->{possible_quote} == 1 );
    my $alternateSemicolonCSV = Text::CSV_XS->new( { allow_loose_quotes => 1, escape_char => '', sep_char => ';' } );

    my @header;

    if ( $self->{delimiter} ) {
        my $lines = $args{lines};
        my $limit = $args{limit} || scalar @$lines;

        my $lineNum = 1;

        for ( my $i = 0 ; $i < $limit ; $i++ ) {
            my $line = $lines->[$i];

            # Make sure the incoming text is properly encoded as UTF8.
            # JPK - In theory, our UTF8::Encode method will work on just about
            # any string we give it.  At least it should work on strings that
            # are UTF8 already (but are not properly flagged), or latin1.
            #
            # !!! In theory, if we set the incoming binmode properly, we do NOT have to do this.
            #     So, if the Common::File class gets it right, this becomes unnecessary.
            #
            $lines->[$i] = Common::UTF8::Encode($line);

            # There shouldn't be a trailing CR on a line, but for some
            # UTF8 files this is not the case. Remove the trailing CR
            # (if present) to prevent $cvs->parse from failing.
            # See Case 13763 for more information.

            # The 'if' here is somewhat pointless
            #
            #            $lines->[$i] =~ s/\x0d$// if ( $line =~ m/\x0d$/ );
            $lines->[$i] =~ s/\x0d$//;

            # Replace any non-tab (0x09) control characters with '?'
            #
            $lines->[$i] =~
s/(\x00|\x01|\x02|\x03|\x04|\x05|\x06|\x07|\x08|\x0a|\x0b|\x0c|\x0d|\x0e|\x0f|\x10|\x11|\x12|\x13|\x14|\x15|\x16|\x17|\x18|\x19|\x1a|\x1b|\x1c|\x1d|\x1e|\x1f)/\?/g;

            my @row;
            if ( $self->{delimiter} eq ',' ) {

                # The "Faithworks" dualtone sales files have some wierd format where
                # some fields have ="data" format even though their csv files that
                # are not '"' qualified. The crude hack below cleans this up.
                $lines->[$i] =~ s/="/"/g;

                # some files have a single " which confuses the parse method
                my @commaCount = $lines->[$i] =~ m/[^\\]"/g;

                #$lines->[$i] =~ s/"// if (scalar @commaCount == 1);

                # a rather gross thing we have to do for CSV files with spaces around the commas.
                # the big drawback is that it get's rid of spaces within quoted strings.
                # we could deal with the quoted fields, but then i forget the point of using
                # the Text::CSV_XS library...
                $lines->[$i] =~ s/\s*,\s*/,/g;

                my $test_quote = $lines->[$i];
                $test_quote =~ s/\",\"//;
                $test_quote =~ s/^\"//;
                $test_quote =~ s/\"$//;
                $self->{possible_quote} = 1 if ( $test_quote =~ /\"/ && !$args{clean_quotes} );

                if ( $args{clean_quotes} == 1 && $self->{possible_quote} == 1 ) {
                    if ( $lines->[$i] !~ /\"/ ) {
                        $lines->[$i] =~ s/,/\t/g;
                    } else {
                        if ( $lines->[$i] =~ /^\"/ && $lines->[$i] =~ /\"$/ && $lines->[$i] =~ /\",\"/ ) {
                            $lines->[$i] =~ s/^\"//;
                            $lines->[$i] =~ s/\"$//;
                            $lines->[$i] =~ s/\",\"/\t/g;
                            $lines->[$i] =~ s/\"/\\\"/g;
                        } else {
                            $lines->[$i] =~ s/\'/\\\'/g;
                            my $subSwitch   = 0;
                            my $workingLine = $lines->[$i];
                            my @comaSubs;
                            my $comaSubsCount = 0;
                            while ( $subSwitch < 1 ) {
                                $workingLine =~ m/(\"[^\"]*\")/;
                                $comaSubs[$comaSubsCount] = $1;
                                my $subField = "ddd" . $comaSubsCount . "bbb";
                                $workingLine =~ s/(\"[^\"]*\")/$subField/;
                                $comaSubs[$comaSubsCount] =~ s/^\"//;
                                $comaSubs[$comaSubsCount] =~ s/\"$//;
                                if ( $workingLine !~ /\"[^\"]*\"/ ) {
                                    $workingLine =~ s/,/\t/g;
                                    $subSwitch = 1;
                                }
                                $comaSubsCount++;
                            }
                            for ( my $x = 0 ; $x < @comaSubs ; $x++ ) {
                                my $subField  = "ddd" . $x . "bbb";
                                my $realField = $comaSubs[$x];
                                $workingLine =~ s/$subField/$realField/;
                            }
                            $lines->[$i] = $workingLine;
                        }
                    }
                    if ( $csv->parse( $lines->[$i] ) ) {
                        @row = split( "\t", $lines->[$i] );
                    } elsif ( $alternateCSV->parse( $lines->[$i] ) ) {
                        @row = split( "\t", $lines->[$i] );
                    } else {
                        $self->errstr("Double quotes must be escaped at line $lineNum");
                        return undef;
                    }
                } else {
                    if ( $csv->parse( $lines->[$i] ) ) {
                        @row = Common::Util::trimquotes( $self->_cleanup( $csv->fields() ) );
                    } elsif ( $alternateCSV->parse( $lines->[$i] ) ) {
                        @row = Common::Util::trimquotes( $self->_cleanup( $alternateCSV->fields() ) );
                    } else {
                        $self->errstr("error parsing file at line $lineNum");
                        return undef;
                    }
                }
            } elsif ( $self->{delimiter} eq ';' ) {

                my $test_quote = $lines->[$i];
                $test_quote =~ s/\";\"//;
                $test_quote =~ s/^\"//;
                $test_quote =~ s/\"$//;
                $self->{possible_quote} = 1 if ( $test_quote =~ /\"/ && !$args{clean_quotes} );

                if ( $args{clean_quotes} == 1 && $self->{possible_quote} == 1 ) {
                    if ( $lines->[$i] !~ /\"/ ) {
                        $lines->[$i] =~ s/;/\t/g;
                    } else {
                        if ( $lines->[$i] =~ /^\"/ && $lines->[$i] =~ /\"$/ && $lines->[$i] =~ /\";\"/ ) {
                            $lines->[$i] =~ s/^\"//;
                            $lines->[$i] =~ s/\"$//;
                            $lines->[$i] =~ s/\";\"/\t/g;
                            $lines->[$i] =~ s/\"/\\\"/g;
                        } else {
                            $lines->[$i] =~ s/\'/\\\'/g;
                            my $subSwitch   = 0;
                            my $workingLine = $lines->[$i];
                            my @comaSubs;
                            my $comaSubsCount = 0;
                            while ( $subSwitch < 1 ) {
                                $workingLine =~ m/(\"[^\"]*\")/;
                                $comaSubs[$comaSubsCount] = $1;
                                my $subField = "ddd" . $comaSubsCount . "bbb";
                                $workingLine =~ s/(\"[^\"]*\")/$subField/;
                                $comaSubs[$comaSubsCount] =~ s/^\"//;
                                $comaSubs[$comaSubsCount] =~ s/\"$//;
                                if ( $workingLine !~ /\"[^\"]*\"/ ) {
                                    $workingLine =~ s/;/\t/g;
                                    $subSwitch = 1;
                                }
                                $comaSubsCount++;
                            }
                            for ( my $x = 0 ; $x < @comaSubs ; $x++ ) {
                                my $subField  = "ddd" . $x . "bbb";
                                my $realField = $comaSubs[$x];
                                $workingLine =~ s/$subField/$realField/;
                            }
                            $lines->[$i] = $workingLine;
                        }
                    }
                    if ( $semicolonCSV->parse( $lines->[$i] ) ) {
                        @row = split( "\t", $lines->[$i] );
                    } elsif ( $alternateSemicolonCSV->parse( $lines->[$i] ) ) {
                        @row = split( "\t", $lines->[$i] );
                    } else {
                        $self->errstr("Double quotes must be escaped at line $lineNum");
                        return undef;
                    }
                } else {
                    if ( $semicolonCSV->parse( $lines->[$i] ) ) {
                        @row = Common::Util::trimquotes( $self->_cleanup( $semicolonCSV->fields() ) );
                    } elsif ( $alternateSemicolonCSV->parse( $lines->[$i] ) ) {
                        @row = Common::Util::trimquotes( $self->_cleanup( $alternateSemicolonCSV->fields() ) );
                    } else {
                        $self->errstr("error parsing file at line $lineNum");
                        return undef;
                    }
                }
            } else {
                @row = Common::Util::trimquotes( $self->_cleanup( split( $self->{delimiter}, $lines->[$i] ) ) );
            }

            $lineNum++;

            push @header, \@row;
        }

        if ( $args{clean_quotes} == 1 && $self->{possible_quote} == 1 ) {
            $self->{possible_quote} = 0;
            $args{clean_quotes} = 0;
        }
    }

    return \@header;
}

1;
