package Greenstone::Package;

use strict;
use warnings;
use utf8;
use File::Basename;
use File::Path 'make_path';
use File::Copy 'cp';
use POSIX 'uname';
use Storable 'dclone';
use Greenstone::Helpers;
use base 'Exporter';

use parent 'Greenstone::Config';

our $VERSION = 1.00;
our @EXPORT = qw(makepkg);

=head1 NAME

Greenstone::Package

=head1 SYNOPSIS

use Greenstone::Package;
makepkg (
    package => 'package',
    distro  => 'distro'
);

=head1 DESCRIPTION

This module will generate scripts for building Greenstone packages
under various Linux distributions

=head2 FUNCTIONS

Only the makepkg function is exported.
All other functions are internal and should not be used externally.

=cut

sub makepkg {
    my %args = @_;

    # sanity checks
    die "A package must have a name" unless (exists $args{package});
    die "A package must have a distro" unless (exists $args{distro});

    my $package_conf = "packages/$args{package}";
    -f $package_conf or die "Package '$args{package}' does not exist";

    my $distro_conf = "distros/$args{distro}";
    -f $distro_conf or die "Distro '$args{distro}' does not exist";

    # Classify ourselves as config and load the config
    my $self = bless({}, 'Greenstone::Config');

    $self->readconf ("global.conf");
    $self->readconf ($distro_conf);

    my $manager_conf = "managers/$self->{config}->{MANAGER}/conf";
    -f $manager_conf or die "Manager config for '$self->{config}->{MANAGER}' does not exist";

    $self->readconf ($manager_conf);

    die "Distro '$args{distro}' is invalid (does not specify a manager)"
        unless (exists $self->{config}->{MANAGER});

    my $class = __PACKAGE__ . '::_' . lc $self->{config}->{MANAGER};
    eval "require $class; 1" or die "Package manager '$self->{config}->{MANAGER}' does not exist or did not compile";

    # Reclassify as our implementation
    bless $self, $class;

    $self->readconf ($package_conf);

    $self->{package} = $args{package};
    $self->{distro} = $args{distro};
    $self->{output} = "build/$args{distro}/$args{package}";
    make_path $self->{output};

    $self->add_sources;
    $self->add_makefile;
    $self->add_package;
}

# Copys the source files required for this package,
# from the files/ folder
# Performs variable substitution on file names,
# and file content for plain-text files
sub add_sources {
    my $self = shift;
    for my $source (@{$self->{config}->{SOURCES}}) {
        $self->add ("files/$source", "$self->{output}/$source", $self->{config});
    }
}

# Generates the Makefile for this package,
# by concatenating the files in the segments/ folder
sub add_makefile {
    my $self = shift;
    push @{$self->{config}->{SOURCES}}, 'Makefile';
    my $makefile = "$self->{output}/Makefile";
    print "    - $makefile\n";
    open MAKEFILE, '>', $makefile
        or die "Failed to open '$makefile' for writing: $!";
    for my $segment (@{$self->{config}->{MAKEFILE}}) {
        open IN, '<', "segments/$segment"
            or die "Failed to open 'segments/$segment' for reading: $!";
        while (my $line = <IN>) {
            $self->subst ($line);
            print MAKEFILE $line;
        }
        close IN;
        print MAKEFILE "\n";
    }
    # add a target for packaging
    my $manager_segment = "segments/$self->{config}->{MANAGER}";
    open IN, '<', $manager_segment
        or die "Failed to open '$manager_segment' for reading: $!";
    while (my $line = <IN>) {
        $self->subst ($line);
        print MAKEFILE $line;
    }
    close IN;
    close MAKEFILE;
}

# Generates the package-manager specific package generation scripts
sub add_package {
    my $self = shift;
    
    # Add the (relatively) static files
    my $static = "managers/$self->{config}->{MANAGER}/files";
    -d $static and $self->add ($static, $self->{output});

    if (exists $self->{config}->{SUB_PACKAGES}) {
        my $packages = [];
        for my $subpackage (sort @{$self->{config}->{SUB_PACKAGES}}) {
            my $config = {};
            for my $key (keys %{$self->{config}}) {
                if ($key =~ /^${subpackage}_(.*)$/) {
                    # print "setting '$1' to '$key' = '$self->{config}->{$key}'\n";
                    $config->{$1} = $self->{config}->{$key};
                }
            }
            # hashdump $self->{config};
            push @{$packages}, $config;
        }
        $self->add_package_impl ($packages);
    } else {
        $self->add_package_impl;
    }
}

# Adds a file or a folder to the package's folder
sub add {
    my ($self, $src, $dst) = @_;
    $self->subst ($dst);

    # make sure the destination path exists
    make_path dirname $dst;

    if (-d $src) {
        mkdir $dst;
        opendir my $DIRFH, $src or die "Could not open '$src': $!";
        while (readdir $DIRFH) {
            next if ($_ eq '.' or $_ eq '..');
            $self->add ("$src/$_", "$dst/$_");
        }
        closedir $DIRFH;
    } else {
        print '    - ', $dst, "\n";
        if (-B $src) {
            # copy binary file
            cp $src, $dst or die "Failed to copy '$src': $!";
        } else {
            # copy normal file
            open IN, '<', $src
                or die "Failed to open '$src' for reading: $!";
            open OUT, '>', $dst
                or die "Failed to open '$dst' for writing: $!";
            while (my $line = <IN>) {
                $self->subst ($line);
                print OUT $line;
            }
            my $perms = (stat IN)[2] & 07777;
            close IN;
            close OUT;
            chmod ($perms | 0600, $dst);
        }
    }
}

1;
