package Distribution::Test::PackageCompare;

use strict;
use Carp;

use lib '/app/tools/common/lib';
use Common::Util;
use Common::RSApp;
use Common::Assert;

use lib '/app/tools/distribution/lib';
use Distribution::Util;
use Distribution::DistributionObject;

use Data::Dumper;
use File::Basename;
use List::Compare;

use base 'Common::XMLObject';

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

    $self->SUPER::_init(@_);

    $self->{_testDir}       = $args{test_dir};
    $self->{_outputDir}     = $args{output_dir};
    $self->{_sourceDir}     = $args{source_dir};
    $self->{_ignoreMissing} = $args{ignore_missing};
    $self->{_ignoreExtra}   = $args{ignore_extra};

    chomp $self->{_testDir};
    chomp $self->{_outputDir};
    chomp $self->{_sourceDir};

    Common::Log::Debug("Test Config Dir: $self->{_testDir}");
    Common::Log::Debug("Output Dir: $self->{_outputDir}");
    Common::Log::Debug("Source Dir: $self->{_sourceDir}");

    return $self;
}

####
#    Methods for running the compare
####

sub run {
    my $self = shift;
    my $error;

    die "Test configuration not found: $self->{_testDir} "
      unless ( -d $self->{_testDir} );

    $error = $self->_validDirectoryStructure();
    $error .= $self->_validChecksums();
    $error .= $self->_validMetadata();

    if ($error) {
        $self->_handleError($error);
        return;
    }

    return 1;
}

sub _handleError {
    my $self = shift;
    my $error = shift || die;

    print "ERROR: $error\n";
}

sub _validDirectoryStructure {
    my $self = shift;

    return;
}

sub _validChecksums {
    my $self  = shift;
    my @files = $self->_getNonMetadataFileList();
    my @sourceFiles;
    my $manifestFile = "$self->{_testDir}/manifest";
    my %checksums;
    my $error;

    open( INFILE, "$manifestFile" ) || die "Failed to open $manifestFile: $!";

    while (<INFILE>) {
        chomp;
        my ( $f, $checksum ) = split /\t/;
        $checksums{$f} = $checksum;
    }

    my @testFiles = keys(%checksums);

    foreach my $f (@files) {
        $f =~ s/.*?\///;
        push( @sourceFiles, $f );
    }

    my $comp = new List::Compare( \@sourceFiles, \@testFiles );

    my @extraFiles   = $comp->get_unique;
    my @missingFiles = $comp->get_complement;

    $error = "Found extra files in the package: @extraFiles\n" if ( @extraFiles && !$self->{_ignoreExtra} );
    $error .= "Missing files in the package: @missingFiles\n" if ( @missingFiles && !$self->{_ignoreMissing} );

    foreach my $sourceFile (@files) {
        next if ( $sourceFile =~ /.*manifest$/ );

        my $testFile   = $sourceFile;
        my $sourcePath = "$self->{_sourceDir}/$sourceFile";

        $testFile =~ s/.*?\///;

        # Don't test if we are ignoring missing files and the file doesn't exist
        next if ( !-e $sourcePath && $self->{_ignoreMissing} );

        my $checksum = Distribution::DistributionObject->_get_file_checksum("$sourcePath");

        if ( $checksums{$sourceFile} ) {
            $error .= "Checksum failed: $sourceFile\n" unless ( $checksums{$testFile} == $checksum );
        }
    }

    return $error;
}

sub _validMetadata {
    my $self      = shift;
    my @files     = $self->_getMetadataFileList();
    my @testFiles = $self->_getTestMetadataFileList();
    my @sourceFiles;
    my $diffOutputPath;
    my $diffIgnore = $self->_getDiffIgnore();
    my $diffOptions = $diffIgnore ? " --ignore-matching-lines='$diffIgnore' " : "";

    my $error;

    my ( $sourcePath, $testPath );

    my $manifest_file = "$self->{_testDir}/manifest";
    my $checksum;

    foreach my $f (@files) {
        my $file = $f;
        $file =~ s/.*?\///;
        push( @sourceFiles, $file );
    }

    my $comp = new List::Compare( \@sourceFiles, \@testFiles );

    my @extraFiles   = $comp->get_unique;
    my @missingFiles = $comp->get_complement;

    $error = "Found extra metadata files in the package: @extraFiles\n" if (@extraFiles);
    $error .= "Missing metadata files in the package: @missingFiles\n" if ( @missingFiles && !$self->{_ignoreMissing} );

    foreach my $sourceFile (@files) {
        $sourcePath = "$self->{_sourceDir}/$sourceFile";

        $testPath = $sourceFile;
        $testPath =~ s/.*?\///;

        next if ( !-e $sourcePath && $self->{_ignoreMissing} );

        $testPath       = "$self->{_testDir}/metadata/$testPath";
        $diffOutputPath = $self->{_outputDir} . "/" . basename($sourceFile) . ".diff";

        my $cmd = "diff $diffOptions \"$sourcePath\" \"$testPath\"";

        Common::Log::Debug("Running: $cmd");

        my $output = `$cmd 2>&1`;

        if ( $output && length($output) ) {
            open( OUT, ">$diffOutputPath" ) || die "Can't open $diffOutputPath: $!";

            print OUT "DIFF CMD: $cmd\n\n";
            print OUT "=================================================================\n";
            print OUT "$output\n";

            close(OUT);

            $error .= "Metadata file comp failed: " . basename($sourceFile);
        }
    }

    return $error;
}

####
#    Methods for creating tests
####

sub create {
    my $self = shift;

    if ( -e "$self->{_testDir}/manifest" ) {
        print STDERR "Will not overwrite configuration for service: $self->{_testDir} exists\n";
        return undef;
    }

    mkdir_local( $self->{_testDir} );

    return $self->_copyMetadataFiles() && $self->_buildManifest();
}

sub _copyMetadataFiles {
    my $self  = shift;
    my @files = $self->_getMetadataFileList();
    my $dir;
    my $cp_cmd;

    my $metadata_dir = "$self->{_testDir}/metadata";

    Common::Log::Debug("Metadata Files: @files\n");

    foreach my $metadata_file (@files) {
        my $destFile = $metadata_file;
        $destFile =~ s/.*?\///;

        $dir = dirname($destFile) if ($destFile);

        mkdir_local("$metadata_dir/$dir");

        die("Failed to create dir: $metadata_dir/$dir")
          unless ( -d "$metadata_dir/$dir" );

        $cp_cmd = "cp \"$self->{_sourceDir}/$metadata_file\" \"$metadata_dir/$destFile\"";

        Common::Log::Debug("Running: $cp_cmd");
        if ( system($cp_cmd ) ) {
            die("CMD Failed: $cp_cmd: $!");
        }
    }

    return 1;
}

sub _buildManifest {
    my $self          = shift;
    my @files         = $self->_getNonMetadataFileList();
    my $manifest_file = "$self->{_testDir}/manifest";
    my $checksum;

    Common::Log::Debug("Non-Metadata Files: @files\n");
    open( OUT, ">$manifest_file" ) || die "Failed to write $manifest_file: $!";

    foreach my $file (@files) {
        $checksum = Distribution::DistributionObject->_get_file_checksum("$self->{_sourceDir}/$file");
        $file =~ s/.*?\///;
        print OUT "$file\t$checksum\n";
    }

    close(OUT);

    return 1;
}

####
# Misc Methods
####

sub _getMetadataFileList {
    my $self = shift;
    my $cmd  = "find $self->{_sourceDir} -mindepth 2 -type f -name '*xml' -o -name '*txt'";
    my @files;

    Common::Log::Debug("Running: $cmd");
    open( FIND, "$cmd |" )
      || die "Failed to run '$cmd'";

    while (<FIND>) {
        chomp;
        s/$self->{_sourceDir}\///;
        push( @files, $_ );
    }

    return @files;
}

sub _getTestMetadataFileList {
    my $self = shift;
    my $cmd  = "find $self->{_testDir}/metadata -type f | grep -v CVS";
    my @files;

    Common::Log::Debug("Running: $cmd");
    open( FIND, "$cmd |" )
      || die "Failed to run '$cmd'";

    while (<FIND>) {
        chomp;
        s/$self->{_testDir}\/metadata\///;
        push( @files, $_ );
    }

    return @files;
}

sub _getNonMetadataFileList {
    my $self = shift;
    my $cmd =
        "find $self->{_sourceDir} -type f "
      . "! -name '*xml' "
      . "! -name '*structure' "
      . "! -name '*original' "
      . "! -name '*src' "
      . "! -name 'checksum' ";
    my @files;

    Common::Log::Debug("Running: $cmd");
    open( FIND, "$cmd |" )
      || die "Failed to run '$cmd'";

    while (<FIND>) {
        chomp;
        s/$self->{_sourceDir}\///;
        push( @files, $_ );
    }

    return @files;
}

sub _getDiffIgnore {
    my $self       = shift;
    my $ignoreFile = "$self->{_testDir}/diff.ignore";

    my $pattern = `cat $ignoreFile` if ( -e $ignoreFile );
    chomp $pattern;

    return $pattern;
}

1;
