#!./bin/perl -I./lib -w

use FindBin qw/ $Bin /;
use Getopt::EvaP;
use File::Basename;
use Time::Local;
use subs qw/ build_game get_choice getSaveStatesForGame getSaveStatesForHuman getSaveStatesTitle /;
use subs qw/ manage_game_save_states listGameSaveStatesForAllGames pick_compiler_path play_game init main fini /;
use subs qw/ remove_initial_game_files setup_initial_game_files /;
use vars qw/ %bgColor2Hex %fgColor2Oct @MM %OPT @PDT $pickCompiler $cmpPath $cpPath $lnPath $lsPath $mkdirPath $rmPath /;
use strict;

init;
main;
fini;

sub main {

    $pickCompiler = pick_compiler_path;

    if ( $OPT{ 'build-game' } ) {
        build_game;
    } elsif ( $OPT{ 'list-game-save-states-for-all-games' } ) {
        listGameSaveStatesForAllGames;
    } else {
        play_game;
    }

} # end main

sub init {
    
    chdir $Bin or warn "chdir $Bin failed : $!"; # platag only works within TAG distribution folder

@PDT = split /\n/, <<'END';
PDT play-text-adventure-games, platag
    game, g : key Adventure, Qork, keyend = D_PLATAG_G, Qork
    manage-game-save-states, s : switch
    game-save-states-folder, gssf : file = D_PLATAG_GSSF, ~/.TAG
    list-game-save-states-for-all-games, l : switch
    terminal-background-color, tbc : string = D_PLATAG_TBC, black
    terminal-text-color, ttc : key black, red, green, yellow, blue, magenta, cyan, white, keyend = D_PLATAG_TTC, white
    build-folder, bf : file = D_PLATAG_BF, `pwd`
    build-game, bg : boolean = D_PLATAG_BG, NO
    f77-compiler, fc : key gnu, intel, pgi, keyend = D_PLATAG_FC, gnu
    
PDTEND no_file_list
END

    @MM = split /\n/, <<"END";

play-text-adventure-games, platag

    Play the games of Adventure and Qork.

    Database files Adventure.text.db and Qork.text.db, as well as the initial game
    save state files Adventure.save and Qork.save are presumed to exist.  Run this
    command with -build-game YES to create them, if you are an Implementer. Use -s
    to manage any Adventure and Qork game save states that you may have.
.game
    Specifies the game you wish to play, Adventure or Qork.
.manage-game-save-states
    Specify -s to manage your saved game states, either to select or remove items.
    When you make a selection from the list of saved states, the game is restored
    from that save state and play is resumed at that point. The default choice is
    the game state from your last SAVE command. Game states you choose to delete
    are removed permanently.
.game-save-states-folder
    Folder containing Adventure and Qork game save state files.
.list-game-save-states-for-all-games
    Simply list all saved games states for every TAG game that you have played.
.terminal-background-color
    Games are played inside on old fashioned Terminal window, use this color for
    the background. The color is either a 6 character hexadecimal RGB triplet like
    3B64C2, or one of the eight named colors allowed for -terminal-text-color.
.terminal-text-color
    Games are played inside on old fashioned Terminal window, use this color for
    the text.
.build-folder
    Pathname to Text Adventure Games distribution and build files.
.build-game
    YES to compile a Text Adventure Game and create the text database and       
    initial save state files.  NO to use them and play.
.f77-compiler
    I have created FORTRAN source that works with the GNU, Intel and PGI compiler
    suites, so make your choice.
END

    EvaP \@PDT, \@MM, \%OPT;	# evaluate parameters

    die "Unknown game '$OPT{ 'game' }', it must be \"Adventure\" or \"Qork\", precisely."
      unless $OPT{ 'game' } =~ m/^Adventure|Qork$/;
    die "Terminal background and terminal text colors may not be the same." if $OPT{ 'terminal-background-color' } eq $OPT{ 'terminal-text-color' };

    %bgColor2Hex = ( qw/ black 000000 red ff0000 green 00ff00 yellow ffff00 blue 0000ff magenta ff00ff cyan 00ffff white ffffff brown a52a2a orange ffa500 purple 800080  / );
    %fgColor2Oct = ( qw/ black     30 red     31 green     32 yellow     33 blue     34 magenta     35 cyan     36 white     37 / );
    
    $cmpPath   = "/usr/bin/cmp";
    $cpPath    = "/bin/cp";
    $lnPath    = "/bin/ln";
    $lsPath    = "/bin/ls";
    $mkdirPath = "/bin/mkdir";
    $rmPath    = "/bin/rm";

} # end init

sub fini {

} # end fini

# Subroutines

sub build_game {

    # Create the game executable and make the first run that creates
    # the initial game save state and random text message database.

    if ( not -s "$OPT{ 'build-folder' }/src/$OPT{ 'game' }.f" or not -s "$OPT{ 'build-folder' }/src/$OPT{ 'game' }.text" ) {
        print "Bah, begone!  You are not an Implementer.\n";
        exit 2
    }

    my $buildStat = system <<"ENDofCMNDS";
    echo Building $OPT{ 'game' }.bin, $OPT{ 'game' }.text.db and $OPT{ 'game' }.save.init ...
    FC='none'
    if [ \"$OPT{ 'f77-compiler' }\" = \"gnu\" ] ; then
        systemLibPath=`xcrun --sdk macosx --show-sdk-path`/usr/lib
        FC="gfortran               -arch x86_64 -w -cpp -fallow-invalid-boz -L\$systemLibPath"
        # FC="gfortran -w -cpp"
    fi
    if [ \"$OPT{ 'f77-compiler' }\" = \"intel\" ] ; then
        FC='ifort -fpp'
        echo '*** NOTE: expect several #6371 warning messages.'
    fi
    if [ \"$OPT{ 'f77-compiler' }\" = \"pgi\" ] ; then
        FC='pgf77 -Mpreprocess -D__PGI_COMPILER'
    fi
    if [ \"\$FC\" = \"none\" ] ; then
        echo Logic error, no FORTRAN 77 compiler found.
        exit 5
    fi
    $pickCompiler
    \$FC -o "$OPT{ 'build-folder' }/bin/$OPT{ 'game' }.bin" "$OPT{ 'build-folder' }/src/$OPT{ 'game' }.f"
    stat=\$?
    if [ \$stat = 0 ] ; then
        $rmPath "$OPT{ 'build-folder' }/etc/$OPT{ 'game' }.save.init" 2> /dev/null
        $rmPath "$OPT{ 'build-folder' }/etc/$OPT{ 'game' }.text.db"   2> /dev/null
        BUILD_$OPT{ 'game' }=1 "$OPT{ 'build-folder' }/bin/$OPT{ 'game' }.bin"
    else
        echo $OPT{ 'game' } build failed due to compiler errors.
    fi
ENDofCMNDS

    $buildStat = $buildStat >> 8;
    die "$OPT{ 'game' } game build failed with error = $buildStat" if $buildStat != 0;

} # end build_game

sub get_choice { # return integer choice

    my $command       = shift;
    my $defaultChoice = shift;
    my $maxChoice     = shift;

    my $ans = -1;
  CHOICE:
    while ( 1 ) {
        print "\nPlease choose which $OPT{ 'game' } game save state to $command, 0 - $maxChoice, or 'q' to quit", ($defaultChoice == -1 ? '' : " [$defaultChoice]"), ">";
        $ans = <>;
        chomp $ans;
        exit 1 if $ans =~ m/^q$/i or $ans =~ m/^quit$/i;
        if ( $ans =~ m/^$/ and $defaultChoice != -1 ) {
            $ans = $defaultChoice;
            last CHOICE;
        }
        if ( $ans =~ m/^\d+$/ and $ans >= 0 and $ans <= $maxChoice ) {
            last CHOICE;
        }
    } # whilend get command answer

    return $ans;

} # end get_choice

sub getSaveStatesForGame {

    my $game = shift;

    my( @gss ) = `$lsPath -t1 "$OPT{ 'game-save-states-folder' }/$game.save"-* 2> /dev/null`;
    chomp @gss;
    @gss = sort inTimeOrder @gss;
        
    my( @gssList );
    foreach my $g ( @gss ) {
        if ( $g =~ m/\/$game.save-.*$/ ) {
            push @gssList, $g;
        }
    }
    return @gssList

} # end getSaveStatesForGame

sub getSaveStatesForHuman {

    my $game = shift;
    my (@gssList) = @_;
    my @humanGSS;

    if ( @gssList ) {
        my $o = 0;
        foreach my $g ( @gssList ) {
            my $b = basename $g;
            my( $dt, $tm, $sc, $mv ) = $b =~ m/^$game.save-(\d\d\d\d\.\d\d\.\d\d)-(\d\d:\d\d:\d\d)-(\d\d\d)-(\d\d\d\d\d\d\d)$/;
            push @humanGSS, sprintf( "%-10s %4d  %s %s %5d %7d%s\n", $game, $o, $dt, $tm, $sc, $mv, '' );
            $o++;
        }
    }
    return @humanGSS

} # end getSaveStatesForHuman

sub getSaveStatesTitle {

    return "Game Save State          When        Score   Moves\n---------------  ------------------- -----  ------\n";

} # end getSaveStatesTitle

sub inTimeOrder { # sort subroutine

    my ($y, $m, $d, $h, $mi, $s) = $a =~ m/(\d\d\d\d)\.(\d\d)\.(\d\d)\-(\d\d):(\d\d):(\d\d)/;
    my $atime = timelocal( $s, $mi, $h, $d, $m-1, $y );
       ($y, $m, $d, $h, $mi, $s) = $b =~ m/(\d\d\d\d)\.(\d\d)\.(\d\d)\-(\d\d):(\d\d):(\d\d)/;
    my $btime = timelocal( $s, $mi, $h, $d, $m-1, $y );
    $atime <=> $btime;
    
} # end inTimeOrder

sub listGameSaveStatesForAllGames {

    my @games;
    foreach my $pdtEntry (@PDT) {
        if ( $pdtEntry =~ m/\s+game,/ ) {
            my( $gamesStr ) = $pdtEntry =~ m/key (.*), keyend/;
            $gamesStr =~ s/ //g;
            @games = split /,/, $gamesStr;
        }
    }
    my @out;
    foreach my $game ( @games ) {
        push @out, getSaveStatesForHuman $game, getSaveStatesForGame $game;
    }
        
    if ( @out ) { print "\n", getSaveStatesTitle, @out; }

} # end listGameSaveStatesForAllGames

sub manage_game_save_states {

    my( @gss ) = getSaveStatesForGame( $OPT{ 'game' } );
    return if $#gss == -1;

    my $currentOrd = 0;
    foreach my $g ( @gss ) {
        my $b = basename $g;
        if ( $b =~ m/^$OPT{ 'game' }.save-.*$/ ) {
            system "$cmpPath \"" . dirname($g) . "/$OPT{ 'game' }.save\" \"$g\" > /dev/null";
            if ( $? == 0 ) { last; }
        }
        $currentOrd++;
    }

  MANAGE:
    while ( 1 ) { # restore or delete?

        print "\nI see ", ( $#gss >= 10 ?  'many' :  [qw/ one two three four five six seven eight nine ten /]->[$#gss] ),
            " $OPT{ 'game' } game save state file", ($#gss == 0 ? '' : 's'), " that you have created, which you may restore\n";
        print "and continue game play from, or delete permanently.\n\n";
        my $o = 0;
        my $defaultChoice = -1;
        my $ans = '';
        print getSaveStatesTitle;
        foreach my $hss ( getSaveStatesForHuman $OPT{ 'game' }, @gss ) {
            my $current = '';
            if ( $o == $currentOrd ) {
                $current = " (current)";
                $defaultChoice = $o;
            }
            $hss =~ s/\n/$current\n/;
            print $hss;
            $o++;
        }
        $o--;

        print "\nRestore ('r') or delete('d') a game save state, 'n' to begin a new game, or 'q' to quit [r]>";
        $ans = <>;
        chomp $ans;
        exit 1 if $ans =~ m/^q$/i or $ans =~ m/^quit$/i;
        if ( $ans =~ m/^r$/i or $ans =~ m/^restore$/i or $ans =~ m/^$/ ) {
            $ans = get_choice "restore", $defaultChoice, $o;
            my $qs = dirname($gss[$ans]) . "/$OPT{ 'game' }.save";
            system "$cpPath \"$gss[$ans]\" \"$qs\"";
            print "Failed restoring game save state: $?\n" if $?;
            print "\n";
            last MANAGE;
        } elsif ( $ans =~ m/^d$/i or $ans =~ m/^delete$/i ) {
            $ans = get_choice "delete", $defaultChoice, $o;
            system "$rmPath \"$gss[$ans]\"";
            print "Failed deleting game save state: $?\n" if $?;
            splice @gss,       $ans, 1;
            last MANAGE if $#gss == -1;
            print "\n";
            next MANAGE
        } elsif ( $ans =~ m/^n$/i or $ans =~ m/^new$/i ) {
            remove_initial_game_files;
            setup_initial_game_files;
            last MANAGE;
        } else {
            print "\n##### Unknown command '$ans', only 'r', 'd', 'n' and 'q' available. #####\n";
            next MANAGE;
        } # ifend restore or delete
	
    } # whilend MANAGE restore or delete?

} # end manage_game_save_states

sub play_game {

    # Play a Text Adventure Game.  Possibly show our player a list of saved
    # games states and allow them to resume play from any one of them.
    #
    # For macOS I have several pre-built static game executables to choose
    # from, and they can play any saved game state.
    #
    # For Linux I have a single static game binary, hope it works for you!

    setup_initial_game_files;

    if ( $OPT{ 'manage-game-save-states' } ) {
        manage_game_save_states;
    }

    my $bgColor = $OPT{ 'terminal-background-color' };
    my $bg = $bgColor2Hex{ lc($bgColor) };
    if ( not defined $bg ) {
        if ( length( $bgColor ) == 6 and $bgColor =~ /^[0-9a-fA-F]+$/ ) {
            $bg = $bgColor;
        } else {
            $bg = $bgColor2Hex{ 'black' };
        }
    }
    my $fg = $fgColor2Oct{ $OPT{ 'terminal-text-color' } };
    my $cmds =  <<"ENDofCMNDS";
    $pickCompiler

    cd "$OPT{ 'build-folder' }/bin"
    if [ -h "$OPT{ 'build-folder' }/bin/$OPT{ 'game' }.bin"  ] || [ ! -f "$OPT{ 'build-folder' }/bin/$OPT{ 'game' }.bin" ]  ; then
        if [ `uname` = "Darwin" ] ; then              # if OS X
            v=`/usr/bin/sw_vers -productVersion`      # 10.x.y
            v=`/bin/echo \$v | /usr/bin/cut -d. -f 1-2` # x
            #v='10.12'
            oslvl=`echo \$v | ./perl -e '
                # Perl hash: key is OS version, value is executable name. Default is *-10.6.bin for unknown OS versions.
                # 10.0  - 10.7  -> *-10.6.bin
                # 10.8  - 10.12 -> *-10.8.bin
                # 10.13 - 10.14 -> *-10.13.bin
                # 10.15 - 11.6  -> *-10.15.bin
                # 12.0  - 12.3  -> *-12.3.bin
                %h = qw/
                10.0  10.6  10.1  10.6  10.2  10.6  10.3  10.6  10.4  10.6  10.5 10.6  10.6 10.6  10.7 10.6
                10.8  10.8  10.9  10.8  10.10 10.8  10.11 10.8  10.12 10.8
                10.13 10.13 10.14 10.13
                10.15 10.15 11.0  10.15 11.1  10.15 11.2  10.15 11.3  10.15 11.4 10.15 11.5 10.15 11.6 10.15
                12.0  12.3  12.1  12.3  12.2  12.3  12.3  12.3/;
                \$oslvl = <>;
                chomp \$oslvl;
                # Current macOS is 12.3, assume future 12.x versions will also work with the 12.3 binary.
                \$oslvl = defined \$h{ \$oslvl} ? \$h{ \$oslvl } : \$oslvl =~ m/^12\\./ ? '12.3' : '10.6';
                print "\$oslvl"'`
            $lnPath -fs osx-static/$OPT{ 'game' }-\$oslvl.bin $OPT{ 'game' }.bin
        else
            arch=`uname -m`
            if [ "\$arch" = "x86_64" ] ; then
                $lnPath -fs linux-static/$OPT{ 'game' }-glibc-2.27.bin $OPT{ 'game' }.bin
            else
                $lnPath -fs linux-static/$OPT{ 'game' }-arm64.bin $OPT{ 'game' }.bin
            fi
        fi
    fi
    cd "$OPT{ 'game-save-states-folder' }"
    printf "\e[${fg}m" # foreground
    printf "\e]11;#$bg\007" # background
    if [ `uname` = "Darwin" ] ; then              # if OS X
        BUILD_$OPT{ 'game' }=0 /usr/bin/arch -x86_64 $OPT{ 'build-folder' }/bin/$OPT{ 'game' }.bin
    else
        BUILD_$OPT{ 'game' }=0 "$OPT{ 'build-folder' }/bin/$OPT{ 'game' }.bin"
    fi

ENDofCMNDS
    system $cmds;

} # end play_game

sub pick_compiler_path {

    # Define a shell command that will be inserted before invoking a game. Here we
    # use "module load", but a script that sets bin and library paths will suffice.

    if ( $OPT{ 'f77-compiler' } eq 'gnu' ) {
        return 'module unload pgi/15.1 2> /dev/null ; module unload intel/15.0.2 2> /dev/null ; module load gcc/4.8.2 2> /dev/null';
    } elsif ( $OPT{ 'f77-compiler' } eq 'intel' ) {
        return 'module unload pgi/15.1 2> /dev/null ; module unload gcc/4.8.2 2> /dev/null ; module load intel/15.0.2 2> /dev/null';
    } elsif ( $OPT{ 'f77-compiler' } eq 'pgi' ) {
        return 'module unload intel/15.0.2 2> /dev/null ; module unload gcc/4.8.2 2> /dev/null ; module load pgi/15.1 2> /dev/null';
    } else {
        die "Logic error, no FORTRAN 77 compiler found.";
    }

} # end pick_compiler_path

sub remove_initial_game_files {

    system <<"ENDofCMNDS";
    $mkdirPath "$OPT{ 'game-save-states-folder' }" 2> /dev/null
    cd "$OPT{ 'game-save-states-folder' }"
    $rmPath $OPT{ 'game' }.save
    $rmPath $OPT{ 'game' }.text.db
ENDofCMNDS

} # end remove_initial_game_files

sub setup_initial_game_files {

    system <<"ENDofCMNDS";
    $mkdirPath "$OPT{ 'game-save-states-folder' }" 2> /dev/null
    cd "$OPT{ 'game-save-states-folder' }"
    err=0
    if [ ! -s "$OPT{ 'game' }.save" ] || [ "$OPT{ 'build-folder' }/etc/$OPT{ 'game' }.save.init" -nt "$OPT{ 'game' }.save" ] ; then
        if [ ! -e "$OPT{ 'build-folder' }/etc/$OPT{ 'game' }.save.init" ] ; then
            echo "An Implementer is required to build $OPT{ 'game' }.save.init"
            err=\$(( err + 1 ))
        else
            $cpPath "$OPT{ 'build-folder' }/etc/$OPT{ 'game' }.save.init" $OPT{ 'game' }.save
        fi
    fi
    if [ ! -s "$OPT{ 'game' }.text.db" ] || [ "$OPT{ 'build-folder' }/etc/$OPT{ 'game' }.text.db" -nt "$OPT{ 'game' }.text.db" ] ; then
        if [ ! -e "$OPT{ 'build-folder' }/etc/$OPT{ 'game' }.text.db" ] ; then
            echo "An Implementer is required to build $OPT{ 'game' }.text.db"
            err=\$(( err + 1 ))
        else
            $cpPath "$OPT{ 'build-folder' }/etc/$OPT{ 'game' }.text.db" $OPT{ 'game' }.text.db
        fi
    fi
    if [ \$err != 0 ] ; then
        exit 5
    fi
ENDofCMNDS
    die "$OPT{ 'game' } setup errors, the Dungeon is closed." if $?;

} # end setup_initial_game_files
