The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Enbld::RcFile;

use strict;
use warnings;

use autodie;
use File::Spec;
use File::Path qw/make_path/;
use File::Temp;
use File::Copy;

use Digest::file qw/digest_file_hex/;
use Digest::SHA  qw/sha1_hex/;

use Carp;

require Enbld::Message;
require Enbld::Error;
require Enbld::Exception;

our @cmd_list = qw/load copy set/;

sub new {
    my $class = shift;

    my $self = {
        command     =>  undef,
        filepath    =>  undef,

        from        =>  undef,

        source      =>  undef,
        url         =>  undef,

        contents    =>  undef,

        directory   =>  undef,

        fullpath    =>  undef,
        filename    =>  undef,

        digest      =>  undef,

        @_,
    };

    bless $self, $class;

    $self->_parse_filepath;
    $self->_validate;

    return $self;
}

sub do {
    my $self = shift;

    if ( $self->{command} eq 'load' && $self->{from} ) {
        $self->{url} = $self->{from};
    }

    if ( $self->{command} eq 'copy' && $self->{from} ) {
        $self->{source} = $self->{from};
    }

    my $cmd    = 'do_' . $self->{command};
    my $result = $self->$cmd;

    return $result;
}

sub _validate {
    my $self = shift;

    _err( "Configuration file's command is not specified." ) unless $self->{command};

    if ( ! grep { $_ eq $self->{command} } @cmd_list ) {
        _err( "'$self->{command}' is invalid command type." );
    }

    _err( "Configuration file's path not set." )     unless $self->{filepath};

    unless ( -d $self->{directory} ) {
		make_path( $self->{directory} );
	}
}

sub _parse_filepath {
    my $self = shift;

    $self->{directory} = $ENV{HOME} unless $self->{directory};

    $self->{fullpath} = File::Spec->file_name_is_absolute( $self->{filepath} ) ?
        $self->{filepath} :
        File::Spec->catfile( $self->{directory}, $self->{filepath} );

    my $dirs;
    ( undef, $dirs, $self->{filename} ) = File::Spec->splitpath( $self->{fullpath} );

    if ( ! _check_permission( $dirs )) {
        _err( "Please check write permission for $dirs." );
    }

    if ( -f $self->{fullpath} ) {
        $self->{digest} = digest_file_hex( $self->{fullpath}, 'SHA-1' );
    }

    return $self->{fullpath};
}

sub _check_permission {
    my $dir = shift;

    my @list = File::Spec->splitdir( $dir );

    while( @list ) {
        my $path = File::Spec->catdir( @list );

        return $path if ( -d -w $path );

        pop @list;
    }

    return;
}

sub do_load {
    my $self = shift;

    _err( "Download URL is not set." ) unless $self->{url};

    _notify( "=====> Load configuration file '$self->{filename}' from '$self->{url}'." );

    require Enbld::HTTP;
    my $temp  = File::Temp->newdir;
    my $path  = File::Spec->catfile( $temp, $self->{filename} );
    my $downloaded = Enbld::HTTP->download( $self->{url}, $path );
    
    unless ( -f -T $downloaded )  {
       _err( "Configuration file '$self->{filename}' isn't text file." ); 
    }

    if ( $self->{contents} ) {
        open my $temphandle, '>>', $downloaded;
        print $temphandle $self->{contents};
        close $temphandle;
    }

    if ( $self->{digest} ) {
        my $digest = digest_file_hex( $path, 'SHA-1' );

        if ( $self->{digest} eq $digest ) {
            _notify( "Configuration file not have the necessity for change." );
            return;
        }

        my ( undef, $dir, $filename ) = File::Spec->splitpath( $self->{fullpath} );

        move( $self->{fullpath}, File::Spec->catfile( $dir, $filename . time ) )
            or _err( $! );
    }

    copy( $path, $self->{fullpath} ) or _err( $! );

    _notify( "=====> Finish configuration file '$self->{filename}'" );

    return $self->{filename};
}

sub do_set {
    my $self = shift;

    _err( "Configuration file's contents isn't set." ) unless $self->{contents};

    _notify( "=====> Set configuration file '$self->{filename}'" );

    my ( undef, $dir, $filename ) = File::Spec->splitpath( $self->{fullpath} );

    if ( $self->{digest} ) {
        if ( $self->{digest} eq sha1_hex( $self->{contents}) ) {
            _notify( "Configuration file not have the necessity for change." );
            return;
        };

        move( $self->{fullpath}, File::Spec->catfile( $dir, $filename . time ))
            or _err( $! );
    }

    make_path( $dir );

    open my $fh, '>', $self->{fullpath};
    print $fh $self->{contents};
    close $fh;

    _notify( "=====> Finish configuration file '$self->{filename}'" );

    return $self->{filename};
}

sub do_copy {
    my $self = shift;

    _err( "Configuration file's source path is not set." ) unless $self->{source};

    unless ( -f -T $self->{source} ) {
        _err( "Configuration file '$self->{filename}' isn't text file." ); 
    }

    _notify( "=====> Copy configuration file '$self->{filename}'" );

    my $temp = File::Temp->newdir;
    my $path = File::Spec->catfile( $temp, $self->{filename} );
    copy( $self->{source}, $path );

    if ( $self->{contents} ) {
        open my $temphandle, '>>', $path;
        print $temphandle $self->{contents};
        close $temphandle;
    }

    if ( $self->{digest} ) {
        my ( undef, $dir, $filename ) = File::Spec->splitpath( $self->{fullpath} );

        my $digest = digest_file_hex( $path, 'SHA-1' );

        if ( $self->{digest} eq $digest ) {
            _notify( "Configuration file not have the necessity for change." );
            return;
        }
        move( $self->{fullpath}, File::Spec->catfile( $dir, $filename . time ) )
            or _err( $! );
    }

    copy( $path, $self->{fullpath} ) or _err( $! );

    _notify( "=====> Finish configuration file '$self->{filename}'" );

    return $self->{filename};
}

sub filename {
    my $self = shift;

    return $self->{filename};
}

sub filepath {
    my $self = shift;

    return $self->{filepath};
}

sub serialize {
    my $self = shift;

    my $serialized;

    $serialized->{filepath}  = $self->{filepath};
    $serialized->{command}   = $self->{command};

    $serialized->{contents}  = $self->{contents} if $self->{contents};
    $serialized->{url}       = $self->{url}      if $self->{url};
    $serialized->{source}    = $self->{source}   if $self->{source}; 

    if ( $self->{directory} ne $ENV{HOME} ) {
        $serialized->{directory} = $self->{directory};
    }

    return $serialized;
}

sub DSL {
    my $self = shift;

    my @rcfile;

    my $str = "conf '" . $self->{filepath} . "' => " . $self->{command} . " {\n";

    push @rcfile, $str;

    if ( $self->{command} eq 'load' ) {
        push @rcfile, '    ' . "from '" . $self->{url} . "';\n";
    }

    if ( $self->{command} eq 'copy' ) {
        push @rcfile, '    ' . "from '" . $self->{source} . "';\n";
    }

    if ( $self->{directory} ne $ENV{HOME} ) {
        push @rcfile, '    ' . "to '" . $self->{directory} . "';\n";
    }

    if ( $self->{contents} ) {
        my @contents = split( "\n", $self->{contents} ); 

        foreach my $line ( @contents ) {
            push @rcfile, '    ' . "content '" . $line . "';\n";
        }
    }

    push @rcfile, "};\n";

    return \@rcfile;
}

sub _err {
    my $err = shift;
    my $param = shift;

    die( Enbld::Error->new( $err, $param ));
}

sub _exception {
    my $exception = shift;
    my $param = shift;

    croak( Enbld::Exception->new( $exception, $param ));
}

sub _notify {
    my $msg = shift;

    Enbld::Message->notify( $msg );
}

1;