#!/usr/bin/perl
# Sina Kazemi, 2010-2016

use strict;
use warnings;
use File::Basename;
use Cwd 'abs_path';

package main;

my $cyanadir = dirname( abs_path($0) );

my @args;
while (@ARGV) {
        my $arg = shift @ARGV;

        if ( $arg eq "-path" ) {
                my $path = shift @ARGV;
                $cyanadir = $path if ($path);
        } else {

                #print "$arg\n";
                push( @args, $arg );
        }
}

my $shell;

sub stop_execution {
        $shell->stoploop;
}

$shell = MyShell->new;

$shell->setCyanaDir($cyanadir) if ($cyanadir);
$shell->setCyanaParam( join( " ", @args ) ) if (@args);

$SIG{INT} = \&stop_execution;

$shell->cmdloop;

####### MYSHELL ######
package MyShell;
use IPC::Open2;
use base "Term::Shell";
use strict;
use warnings;
use threads (
        'yield',
        'stack_size' => 64 * 4096,
        'exit'       => 'threads_only',
        'stringify'
);

my $cyana = "cyana";

sub start_thread {
        my $o = shift;
        my ( $readcyana, $writecyana ) = ( $o->{read}, $o->{write} );

        my $got;
        while ($readcyana) {
                $got = <$readcyana>;
                last if ( !$got );
                $got =~ s/cyana>//g;
                print $got;
        }
}

sub run_more {
        my $o     = shift;
        my @param = @_;

        foreach (@param) {
                open( FILE, "<$_" ) || next;
                my @lines = <FILE>;
                close(FILE);
                $o->page( join( "", @lines ) );
        }
}

sub run_quit {
        my $o = shift;

        writeHistory($o);
        $o->cmd(exit);
}

sub run_q {
        my $o = shift;

        run_quit($o);
}

sub run_history {
        my $o = shift;

        my @hist = $o->{term}->GetHistory;
        my $len  = 1 + log(@hist) / log(10);

        for my $i ( 0 .. $#hist ) {
                print spaceadd( $i + 1, $len ), ": $hist[$i]\n";
        }
}

sub run_clear {
        my $o = shift;

        $o->{term}->SetHistory( () );
}

sub addspace {
        my $text   = shift;
        my $length = shift;

        for my $i ( length($text) .. $length - 1 ) {
                $text .= " ";
        }

        return $text;
}

sub writeHistory {
        my $o = shift;

        my $write = open( LOG, ">$o->{historyPath}" );
        if ($write) {
                my @hist = $o->{term}->GetHistory;
                my $max  = 0;
                foreach (@hist) {
                        $_ =~ s/ +$//;
                        $max = length($_) if ( $max < length($_) );
                }

                my $text;
                foreach (@hist) {
                        $text = addspace( $_, $max );
                        print LOG $text, "   # session: ", $o->{sessionTime}, "\n";
                }
                close(LOG);

        } else {
                print "WARNING: could not write log to '$o->{historyPath}'\n";
        }
}

sub spaceadd {
        my $text   = shift;
        my $length = shift;

        for my $i ( length($text) .. $length - 1 ) {
                $text = " " . $text;
        }

        return $text;
}

sub postloop {
        my $o = shift;
        my ( $readcyana, $writecyana ) = ( $o->{read}, $o->{write} );

        writeHistory($o);

        print $writecyana "quit\n";
        waitpid( $o->{pid}, 0 );
}

sub catch_run {
        my $o   = shift;
        my $cmd = shift;
        my ( $readcyana, $writecyana ) = ( $o->{read}, $o->{write} );

        if ( $cmd =~ /^!(\d+)\-(\d+)/ || $cmd =~ /^!(\d+)/ ) {
                my @hist = $o->{term}->GetHistory;
                pop(@hist);
                $o->{term}->SetHistory(@hist);
                my $start = $1 - 1;
                my $stop  = $1 - 1;
                $stop = $2 - 1 if ($2);

                for my $index ( $start .. $stop ) {
                        last if ( $index >= @hist );
                        next if ( $index < 0 );
                        $o->cmd( $hist[$index] . "\n" );
                        push( @hist, $hist[$index] );
                        $o->{term}->SetHistory(@hist);
                }
        } else {
                cyanaCmd( $o, $o->{line} );
        }
}

sub prompt_str {
        "cyana> ";
}

sub catch_smry {
        my $o   = shift;
        my $cmd = shift;

        if ( $o->{commands}->{$cmd} ) {
                return $o->{commands}->{$cmd}->{help};
        }

        return;
}

sub catch_comp {
        my $o   = shift;
        my $cmd = shift;
        my $ext = shift;

        my @param;

        #print "[$ext]";
        #       if ( $ext =~ /^\.|^\// ) {
        my @path = split( /\//, $ext );

        my $file;

        my $dir = join( "/", @path );

        $dir =~ s /^~/$ENV{HOME}/s if ( defined $ENV{HOME} );
        if ( -d $dir ) {
                $file = "";
        } else {
                $file = pop @path;
                $file = "" if ( !$file );
                $dir  = join( "/", @path );
        }

        my $loop = 1;

        if ( $dir eq "" ) {
                if ( $file eq "" ) {
                        $loop = 0;
                } else {
                        $dir = ".";
                }
        }

        $loop = 0 if ( $dir eq "" && $file eq "" );

        #               do {
        while ($loop) {
                $loop = 0;
                my $ok = opendir( DIR, $dir . "/" );
                if ($ok) {
                        my @files = readdir(DIR);
                        closedir(DIR);
                        my $count = 0;
                        foreach (@files) {
                                next if ( $_ eq ".." || $_ eq "." );

                                #                               print "> $_\n";
                                if ( $count > 1000 ) {
                                        push( @param, "### Too many files list truncated ###" );
                                        last;
                                }
                                push( @param, $dir . "/" . $_ ) if ( $_ =~ /^$file/ );
                                $count++;
                        }

                        if ( @param == 1 && -d $param[0] ) {
                                $dir  = $param[0];
                                $file = "";
                                $loop = 1;
                        }
                }
        }

        if ( $o->{commands}->{$cmd} ) {
                foreach ( @{ $o->{commands}->{$cmd}->{param} } ) {
                        push( @param, $_ ) if ( $_ =~ /^$ext/ );
                }
        }

        return @param;
}

sub cyanaCmd {
        my $o = shift;
        my $commands = join "\n", @_, "print (DONE)\n";
        my ( $readcyana, $writecyana ) = ( $o->{read}, $o->{write} );

        #print "CMD> $commands\n";
        print $writecyana $commands;

        $readcyana || die "ERORR: CYANA crashed.\n";

        my $go     = 1;
        my $output = "";
        while ( $readcyana && $go ) {
                my $got = <$readcyana>;

                last unless defined $got;

                if ( $got =~ /(FATAL ERROR):/ ) {
                        print $got;
                        $o->cmd("quit");
                } elsif ( $got =~ s/^.+?\(DONE\)$// ) {
                        $go = 0;
                        next;
                }

                $got =~ s/cyana> //g;

                print $got if ($go);
                $output .= $got;
        }
        $output =~ s/cyana> //g;

        return $output;
}

sub cyanaCmdLines {

        my $output = cyanaCmd(@_);
        my @lines = split /\n/, $output;
        chomp(@lines);

        return @lines;
}

sub AUTOLOAD {
        my $o = shift;
        our $AUTOLOAD;

        #print ">>$AUTOLOAD -> $o\n";

        if ( $AUTOLOAD =~ /::help_(.+)/ ) {
                my $cmd = $1;

                if ( $o->{commands}->{$cmd} ) {

                        my ( $readcyana, $writecyana ) = ( $o->{read}, $o->{write} );

                        #print $writecyana $o->{line}, "\n";
                        cyanaCmd( $o, $o->{line} );
                } else {
                        $o->catch_help;
                }
        }

        elsif ( $AUTOLOAD =~ /::run_(.+)/ ) {
                my $cmd = $1;

                if ( $o->{commands}->{$cmd} ) {
                        my ( $readcyana, $writecyana ) = ( $o->{read}, $o->{write} );

                        #print $writecyana $o->{line}, "\n";
                        cyanaCmd( $o, $o->{line} );
                } else {
                        $o->cmd( $o->{line} );
                }
        }
}

#sub run_print {
#       my $o    = shift;
#       my @args = @_;
#       print @args, "\n";
#}

sub parseVariable {
        my $lines = shift;
        my %commands;
        my $cmd;
        my $help;

        foreach my $line (@$lines) {
                $line =~ s/^ *//;
                $line =~ s/\n//;

                #print $line, "\n";
                if ( $line =~ /(.+) +\- +(.+)/ ) {
                        $cmd  = $1;
                        $help = $2;
                        my @list = split( /[ ,]+/, $cmd );

                        my %param;
                        $param{cmd} = shift @list;

                        %param = %{ $commands{ $param{cmd} } } if ( $commands{ $param{cmd} } );
                        my @p;
                        if ( $param{param} ) {
                                @p = @{ $param{param} };
                        }

                        foreach (@list) {
                                push( @p, $_ );
                        }
                        $param{param}            = \@p;
                        $param{help}             = $help;
                        $commands{ $param{cmd} } = \%param;
                } elsif ( $line =~ /(.+) += +(.+)/ ) {
                        $cmd = $1;
                        $cmd =~ s/^ +//;
                        $cmd =~ s/ +$//;
                        if ( !$commands{$cmd} ) {
                                my %param = ( cmd => $cmd, help => "CYANA variable\ndefault value: $2" );

                                #$commands{ $param{cmd} } = \%param;
                        }
                }
        }
        return \%commands;
}

sub setCyanaDir {
        my $o    = shift;
        my $path = shift;

        $o->{cyanadir} = $path;
}

sub setCyanaParam {
        my $o     = shift;
        my $param = shift;

        $o->{cyanaparam} = $param;
}

sub setPreHistory {
        my $o    = shift;
        my @hist = @_;

        print "setPreHistory @hist\n";
        $o->{prehistory} = \@hist;
}

sub getPreHistory {
        my $o = shift;

        my @hist = $o->{cyanaparam} ? @{ $o->{cyanaparam} } : ();

        return @hist;
}

sub preloop {
        my $o = shift;

        $o->{'term'}->Attribs->ornaments(0);

        $o->{cyana} .= " " . $o->{cyanaparam};
        $o->{cyana} = $o->{cyanadir} . "/" . $o->{cyana} if ( $o->{cyanadir} );

        if ( !-e $o->{cyanadir} ) {
                print "WARNING: No cyana path set. Please specify parameter '-path' or set enviroment variable '\$myCyanaDir'\n";

                my $path;
                do {
                        if ( defined $path && $path ne "" ) {
                                print "file '$path/cyana' not found.\n";
                        }
                        print "path to cyana directory (or q): ";
                        $path = <>;
                        chomp($path);
                        die "ERROR: no cyana path specified.\n" if ( $path eq "q" );
                } while ( !-e $path . "/cyana" );
                $o->{cyanadir} = $path;
                $o->{cyana}    = $o->{cyanadir} . "/cyana";
        }
        $o->{cyana} =~ s/\s+$//;
        print "CYANA command: '", $o->{cyana}, "'\n";

        my ( $readcyana, $writecyana, $cyanaerr, $log );

        $o->{historyPath} = ( $ENV{HOME} ? $ENV{HOME} : "." ) . "/.cyanashell";
        $o->{historyLength} = 100;

        $o->{sessionTime} = localtime;

        my $read = open( LOG, "<$o->{historyPath}" );
        if ($read) {
                my @readlines = <LOG>;
                my $start     = 0;
                $start = @readlines - $o->{historyLength} if ( @readlines > $o->{historyLength} );

                my ( @lines, @dates );
                foreach ( my $i = $start ; $i < @readlines ; $i++ ) {
                        my @words = split( /[#\n]/, $readlines[$i] );
                        next if ( $words[0] =~ /^ +$/ );
                        $words[0] =~ s/ +$//;
                        push( @lines, $words[0] );
                        push( @dates, $words[1] );
                }
                $o->{term}->SetHistory(@lines);

                #$o->{term}->addhistory(@lines);
                $o->SetPreHistory(@dates);
        }

        my $pid = open2( $readcyana, $writecyana, "$o->{cyana}" ) || die "ERROR: could not run command '$o->{cyana}'\n";
        print $writecyana "help\n";

        #print $writecyana "show\n";
        print $writecyana "quit\n";
        waitpid( $pid, 0 );

        my @lines    = <$readcyana>;
        my $commands = parseVariable( \@lines );

        @lines = <$readcyana>;
        foreach (@lines) {
                print $_;
        }

        $o->{commands} = $commands;

        foreach my $cmd ( keys %$commands ) {

                $o->add_handlers( "run_" . $cmd );
                $o->add_handlers( "help_" . $cmd );
        }

        $o->{pid} = open2( $readcyana, $writecyana, "$o->{cyana}" );

        ( $o->{read}, $o->{write} ) = ( $readcyana, $writecyana );

        cyanaCmd( $o, "" );
}

sub init {
        my $o = shift;

        $o->{cyana}      = "cyana";
        $o->{cyanaparam} = "";
}
