#!/usr/bin/perl

use Cwd;
use File::Basename;

#use Getopt::Long qw(:config no_ignore_case bundling_override);
use Getopt::Long qw(:config no_ignore_case bundling);

#use Term::ANSIColor;
#use Term::ANSIColor qw(:constants);

##############################################################################
#                                                                            #
# OS type detection                                                          #
#                                                                            #
##############################################################################

my $ostype = $^O =~ /MSWin/i ? "windows" : "unix";
my $pd = $ostype =~ /windows/i ? "\\" : "/";
my $dd = $ostype =~ /windows/i ? ";" : ":";
my $obj = $ostype =~ /windows/i ? ".obj" : ".o";

##############################################################################
#                                                                            #
# Configuration area                                                         #
#                                                                            #
##############################################################################

my @include_path_default;
my $ow_path;
my $ow_path;

# *NIX path configurations
if ( $ostype =~ /unix/ )
{

# List of default include dirs
# In own quotes, separated by comma
@include_path_default = ( "/opt/qnx4/usr/include" );

# List of default library dirs
# Into quotes, separated by colon
$library_path_default = ( "/opt/qnx4/usr/lib" );

# Path to Open Watcom bin directory
$ow_path = "/opt/watcom/binl";

}

# Windows path configurations
elsif ( $ostype =~ /windows/ )
{

# List of default include dirs
# In own quotes, separated by comma
@include_path_default = ( "C:\\WATCOM\\h\\qnx4" );

# List of default library dirs
# Into quotes, separated by colon
$library_path_default = ( "C:\\WATCOM\\lib386\\qnx4\\newbuild;C:\\WATCOM\\lib386;C:\\WATCOM\\lib386\\qnx4\\original" );

# Path to Open Watcom bin directory
$ow_path = ("C:\\WATCOM\\binnt");

}

##############################################################################
#                                                                            #
# Known bugs:                                                                #
#                                                                            #
#                                                                            #
#                                                                            #
##############################################################################

##############################################################################
#                                                                            #
# TODO:                                                                      #
#                                                                            #
# 1. Options -B and -b                                                       #
#                                                                            #
##############################################################################

##############################################################################
#                                                                            #
# Internal data and program area (do not change)                             #
#                                                                            #
##############################################################################

my $version = "1.01D/14.02.2017";

my $wcc = "wcc386";
my $wpp = "wpp386";

my $defs;
my @include_path;
my $library_path;
my @libraries;

my @com_opts = ( "-zq" );
my @s_opts;
my @c_opts;
my @C_opts;
my @d_opts;
my @l_opts = ( "op quiet" );
my @p_opts;
my %pass_opts = ( 's' => \@s_opts, 'c' => \@c_opts, 'C' => \@C_opts,
				  'd' => \@d_opts, 'l' => \@l_opts, 'p' => \@p_opts );
my @L_opts = ( "-b -c" );

my $cpu_type = "4r";
my $no_link = 0;
my $mk_lib = 0;
my $pproc_out = 0;
my $pproc_file = 0;
my $same_dir = 0;
my $map_file = 0;
my $mem_model;
my $stack;
my $out_file;
my $lib_file;

my $quiet = 0;
my $assembly = 0;
my $priv_level = 3;
my $noexec;
my $strip;
my $usage_file;

my $debug_link;

my $error_msg;
my $evil_hack;


sub processor_type {
	$_[1] = "" unless $_[0] ge "3";
	$_[1] = "" unless $_[1] eq "s" or $_[1] eq "r";
	$_[1] = "r" if $_[1] eq "" and $_[0] ge "3";
#	$_[0] = "" if $_[0] le "1";
	$cpu_type =  $_[0] . $_[1];
}

sub define_macro {
	if ( $_[0] eq "D" and $defs ne "d" ) {
		push @com_opts, "-d+";
		$defs = "d";
	}

	if ( $_[0] eq "D" ) {
		$_[1] =~ s/\"/\\\"/g;
	}

	push @com_opts, "-" . ( $_[0] eq "D" ? "d" : "u" ) . "\"" . $_[1] . "\"";
}

sub debug_type {
	my $dl = substr $_[1], 0, 1;
	my $dt = substr $_[1], -1, 1;

#	push @com_opts, ( $dl lt "1" or $dl gt "3" ? "-d2" : "-d$dl" );

	if ( $dl =~ /[1-3]/ ) {
		push @com_opts, "-d$dl";
	}
	else {
		push @com_opts, "-d2";
		$dt = $dl;
	}

	push @com_opts, "-h$dt" if $dt =~ /^[wdc]$/;
	$debug_link = $dt eq "w" ? "watcom" :
				  $dt eq "d" ? "dwarf" :
				  $dt eq "c" ? "codeview" : "all";
}

sub pass_cmd {
	my $cmd = substr( $_[1], 0, 1 );

	if ( $_[1] !~ /^[scCdlp]/ ) {
		$error_msg = "Unknown letter ($cmd) for -$_[0]$_[1]\n";
		die "!FINISH";
	}
	elsif ( $_[1] !~ /^[scCdlp],.*/ ) {
		$error_msg = "Incorrect command format for -$_[0]$_[1]\n";
		die "!FINISH";
	}

	push @{$pass_opts{$cmd}}, substr( $_[1], 2 );
}

sub print_version {
	$error_msg = "cc - compile command (UNIX), version $version, (c) CBD BC, Oleg Bolshakov\n";
	die "!FINISH";
}

sub find_library {
	my $lib = $_;

	if ( $_ =~ /^\/|^\.\/|^\.\.\// ) {
		goto found if -e $lib;
	}

	my @libs = ( $_ =~ /\.lib$/ ? $_ : $_ . ".lib" );
	if ( $_ !~ /\.lib$/ ) {
		unshift @libs, $_ . "3r.lib" if ( $cpu_type =~ /[3-5]r/ );
		unshift @libs, $_ . "3s.lib" if ( $cpu_type =~ /[3-5]s/ );
		unshift @libs, $_ . $mem_model . ".lib" if ( $cpu_type =~ /[0-2]/ );
	}

	L: foreach $path ( split( $dd, $library_path ) ) {
		foreach $l ( @libs ) {
			if ( -e "$path/$l" ) {
				$lib = "$path/$l";
#				$lib = $l;
				last L;
			}
		}
	}


	found: push @l_opts, "l $lib";
}


# Fix -g and -O occurrence
# Also fix -0 .. -5
for ( $i = 0; $i < scalar @ARGV; $i++ ) {
	$ARGV[$i] = "-g2" if $ARGV[$i] eq "-g";
	$ARGV[$i] = "-Oil" if $ARGV[$i] eq "-O";
	$ARGV[$i] =~ s/(^-[0-5]$)/\1r/ if $ARGV[$i] =~ /^-[0-5]$/;
}

# Fix continuous options (-0 .. -5, -f, -g, -m, -O, -W)
for ( $i = 0; $i < scalar @ARGV; $i++ ) {
	die( "Option $ARGV[$i] has no argument\n" ) if $ARGV[$i] =~ /^-[0-5fgmOW]$/;
}


$result = GetOptions( "0:s"		=> \&processor_type,
					  "1:s"		=> \&processor_type,
					  "2:s"		=> \&processor_type,
					  "3:s"		=> \&processor_type,
					  "4:s"		=> \&processor_type,
					  "5:s"		=> \&processor_type,

					  "A=s"		=> sub { $mk_lib = 1; push @L_opts, $_[1]; unshift @L_opts, "-q -n"; },
					  "a=s"		=> sub { $mk_lib = 2; push @L_opts, $_[1]; unshift @L_opts, "-q"; },
#					  "B"		=> 
#					  "b"		=> 

					  "c"		=> \$no_link,
					  "D=s"		=> \&define_macro,
					  "E"		=> sub { $pproc_out = 1; $no_link = 1; },
					  "F"		=> \$same_dir,
					  "f=s"		=> sub { push @com_opts, "-f$_[1]"; },
					  "g:s"		=> \&debug_type,
					  "I=s"		=> \@include_path,
					  "j"		=> sub { push @com_opts, "-j"; },
					  "L=s"		=> sub { $library_path .= $dd . $_[1] },
					  "l=s"		=> \@libraries,
					  "M"		=> \$map_file,
					  "m=s"		=> \$mem_model,
					  "N=s"		=> \$stack,
					  "O:s"		=> sub { push @com_opts, ( $_[1] ? "-o$_[1]" : "-oil" ); },
					  "o=s"		=> \$out_file,
					  "P"		=> sub { $pproc_file = 1; $no_link = 1; },
					  "Q"		=> \$quiet,
					  "R=s"		=> \$usage_file,
					  "S"		=> sub { $assembly = 1; $no_link = 1; push @com_opts, "-d1"; },
					  "s"		=> \$strip,
					  "T=s"		=> \$priv_level,
					  "U=s"		=> \&define_macro,
					  "v=s"		=> sub {},
					  "W=s"		=> \&pass_cmd,
					  "w=i"		=> sub { push @com_opts, "-w$_[1]"; },
					  "X"		=> \$noexec,

#					  "x"		=>

					  "z=s"		=> sub { push @c_opts, "-z$_[1]"; push @C_opts, "-z$_[1]"; },

# New options
					  "V"		=> \&print_version,
					  "!"		=> \$evil_hack,
					);

# Check error cases
#die( "Error in command line\n" ) if qw( $error );
die( "\n" ) if $error;
die( $error_msg ) if $error_msg;

# Add default include paths
push @include_path, @include_path_default;

# Add default library paths
$library_path .= $dd . $library_path_default;
$library_path .= $dd . $ENV{'LIBQNX'} if defined $ENV{'LIBQNX'};
$library_path = substr( $library_path, 1 ) . $dd . ".";

# Set default stack size
unless ( defined $stack ) {
	$stack = $cpu_type =~ /^[0-2]/ ? "4k" : $mem_model eq "s" ? "8k" : "32k";
}

# Set compilator name
$wcc = "wcc" if $cpu_type =~ /^[0-2]/;
$wpp = "wpp" if $cpu_type =~ /^[0-2]/;

##############################################################################
#                                                                            #
# Process options                                                            #
#                                                                            #
##############################################################################

# Set memory model
unshift @l_opts, $mem_model ne "s" ? "form qnx flat" : "form qnx";
push @com_opts, "-m" . ( $mem_model ? $mem_model : "s" );
$mem_model = "s" if not $mem_model;

# Set output name
if ( $no_link and not $assembly ) {
	push @com_opts, "-fo=" . $out_file if defined $out_file;
}

# Set preprocessor options
if ( $pproc_out or $pproc_file ) {
	push @com_opts, "-plc";
	push @com_opts, "-pw=0";
	push @com_opts, @p_opts;
}

# Set arch type
push @com_opts, "-" . $cpu_type if $cpu_type !~ /^[01]/;

# Set include path
push @com_opts, "-i=" . $_ foreach @include_path;

# Add options
unshift @s_opts, @com_opts;
unshift @c_opts, @com_opts;
unshift @C_opts, @com_opts;

# Set output name
push @l_opts, "na " . ( $out_file ? $out_file : "a.out" );

# Set map file
push @l_opts, "op map=" . ( $out_file ? "$out_file.map" : "a.out.map" ) if $map_file;

# Set priviti level
push @l_opts, "op priv=$priv_level";

# Set case exact
push @l_opts, "op c";

# Set library path
push @l_opts, "libp $library_path";

# Set usage
push @l_opts, "op res=$usage_file" if $usage_file;

# Set debug type
push @l_opts, "de $debug_link" if $debug_link;

# Link libraries
&find_library( $_ ) foreach @libraries;

# Compile sources
foreach ( @ARGV ) {
	my $ofile = $_;
	my $cc;
	my $cmd;

	if ( $pproc_file ) {
		$ofile =~ s/\.[^\/]+$/\.i/;
	}
	elsif ( /$obj$/ ) {
		# Nothing to do
	}
	elsif ( /\.lib$/ ) {
		# Nothing to do
	}
	else {
		$ofile =~ s/\.[^\/]+$/$obj/;
		if ( not $same_dir ) {
			$ofile = basename( $ofile );
		}
	}

	$cc = $wcc if /\.c$/;
	$cc = $wpp if /\.cpp$|\.C$/;
	$cc = "wasm" if /\.S$|\.s$|\.asm$/;

	if ( $cc ) {
		$cmd = "$ow_path/$cc " .
			   join( " ", ( $cc eq $wcc ? @c_opts : $cc eq $wpp ? @C_opts : @s_opts ) ) .
			   ( ( $same_dir or $pproc_file ) ? " -fo=$ofile" : "" ) . " $_";
		print "$cmd\n" if not $quiet;
		system $cmd if not $noexec;
		die( "$cc return error status ($?)\n" ) if $?;
	}
	elsif ( /$obj$/ ) {
		# Nothing to do
	}
	elsif ( /\.lib$/ ) {
		# Nothing to do
	}
	else {
		die( "No compiler available for `$_'\n" );
	}

	if ( $mk_lib ) {
		push @L_opts, ( $mk_lib eq 2 ? "-+" : "" ) . ( ( not $same_dir ) ? getcwd() . $pd : "" ) . "$ofile";
	} else {
		push @l_opts, "f " . ( ( not $same_dir ) ? getcwd() . $pd : "" ) . "$ofile";
	}

	# Disassembly files
	if ( $assembly and $_ !~ /\.S$|\.s$|\.asm$/ ) {
		$cmd = "$ow_path/wdis -s $ofile -l=";
		$ofile =~ s/\.[^\/]+$/\.S/;
		$cmd .= "$ofile";
		print "$cmd\n" if not $quiet;
		system $cmd if not $noexec;
		die( "wdis return error status ($?)\n" ) if $?;
	}
	
}

#die( "\n" ) if $no_link;

# Set offset
push @l_opts, "op offset=" . ($stack + 8) . "k" if $mem_model ne "s";

# Set Stack size
push @l_opts, "op st=$stack";

# Create library
if ( $mk_lib ) {
	my $cmdl = "$ow_path/wlib " . join( " ", @L_opts );
	print "$cmdl\n" if not $quiet;
	system $cmdl if not $noexec;
	die( "wlib return error status ($?)\n" ) if $?;
}

# Link object files
if ( not $no_link and not $mk_lib ) {
	my $cmdl = "$ow_path/wlink " . join( " ", @l_opts );
	print "$cmdl\n" if not $quiet;
	system $cmdl if not $noexec;
	die( "wlink return error status ($?)\n" ) if $?;
}

# Strip executable file
if ( $strip ) {
	my $cmds = "$ow_path/wstrip " . ( $out_file ? $out_file : "a.out" );
	print "$cmds\n" if not $quiet;
	system $cmds if not $noexec;
	die( "wstrip return error status ($?)\n" ) if $?;
}

