#!/usr/bin/perl -w

use strict;

package Common;

use POSIX;
#use Data::Dumper;

sub simpleSplitArg($) {
    # split $arg into tokens keeping all chars except whitespace between tokens
    my $arg = shift;

    chomp $arg;
    my @str = split(//, $arg);

    my @argv = ();
    my @token = ();

    my %S = (
	word => 0,
	q    => 2,
	qq   => 3,
	ws   => 4,
    );

    push @str, "Tend";
    push @str, "Tend";
    my $state = $S{ws};
    for (my $ix = 0; $ix < @str; $ix++) {
	my $cc  = $str[$ix];
	my $nxt = $str[$ix+1];

	if    ($cc eq 'Tend' ) {
	    if      ($state == $S{word}) {
		push @argv, join("", @token); @token = ();
	    } elsif ($state == $S{q}) {
		die("simpleSplitArg error: unterminated ' <$arg>");
	    } elsif ($state == $S{qq}) {
		die("simpleSplitArg error: unterminated \" <$arg>");
	    } elsif ($state == $S{ws}) {
		# ignore trailing whitespace
	    } else {
		die("simpleSplitArg error: unknown state $state");
	    }
	    last;

	} elsif ($cc =~ m/\s/ ) {
	    if      ($state eq $S{word}) {
		push @argv, join("", @token); @token = ();
		$state = $S{ws};
	    } elsif ($state == $S{q}) {
		push @token, $cc;
	    } elsif ($state == $S{qq}) {
		push @token, $cc;
	    } elsif ($state == $S{ws}) {
		# ignore ws between tokens
	    } else {
		die("simpleSplitArg error: unknown state $state");
	    }

	} elsif ($cc eq "'" ) {
	    if      ($state == $S{word}) {
		push @token, $cc;
	    } elsif ($state == $S{q}) {
		push @token, $cc;
		if ($nxt =~ m/\s/ || $nxt eq 'Tend') {
		    $state = $S{word};
		}
	    } elsif ($state == $S{qq}) {
		push @token, $cc;
	    } elsif ($state == $S{ws}) {
		push @token, $cc;
		$state = $S{q};
	    } else {
		die("simpleSplitArg error: unknown state $state");
	    }

	} elsif ($cc eq '"' ) {
	    if      ($state == $S{word}) {
		push @token, $cc;
	    } elsif ($state == $S{q}) {
		push @token, $cc;
	    } elsif ($state == $S{qq}) {
		push @token, $cc;
		if ($nxt =~ m/\s/ || $nxt eq 'Tend') {
		    $state = $S{word};
		}
	    } elsif ($state == $S{ws}) {
		push @token, $cc;
		$state = $S{qq};
	    } else {
		die("simpleSplitArg error: unknown state $state");
	    }

	} elsif ($cc =~ m/[[:print:]]/ ) {
	    if      ($state == $S{word}) {
		push @token, $cc;
	    } elsif ($state == $S{q}) {
		push @token, $cc;
	    } elsif ($state == $S{qq}) {
		push @token, $cc;
	    } elsif ($state == $S{ws}) {
		push @token, $cc;
		$state = $S{word};
	    } else {
		die("simpleSplitArg error: unknown state $state");
	    }

	} else {
	    die("simpleSplitArg error: unknown input <$cc> in <$arg>");
	}
    }

    @argv;
}

sub splitArg($) { ## TODO
    # split $arg as the shell would do it
    my $arg = shift;
    chomp $arg;
    my @str = split(//, $arg);

    my @argv = ();
    my @token = ();

    my %S = (
	word => 0,
	s    => 1,
	q    => 2,
	qq   => 3,
	ws   => 4,
    );

    push @str, "Tend";
    my $state = $S{ws};
    for (my $ix = 0; $ix < @str; $ix++) {
	my $cc  = $str[$ix];

#	print "\n";
#	print "cc = <$cc>\n";
#	print "state = $state\n";
#	print "token: <", join("> <", @token), ">\n";

	if    ($cc eq 'Tend' ) {
	    if      ($state == $S{word}) {
		push @argv, join("", @token);
#		print "argv: <", join("> <", @argv), ">\n";
	    } elsif ($state == $S{s}) {
		die("splitArg error: unterminated \\");
	    } elsif ($state == $S{q}) {
		die("splitArg error: unterminated '");
	    } elsif ($state == $S{qq}) {
		die("splitArg error: unterminated \"");
	    } elsif ($state == $S{ws}) {
		# ignore trailing whitespace
	    } else {
		die("splitArg error: unknown  state");
	    }
	    last;

	} elsif ($cc =~ m/\s/ ) {
	    if      ($state eq $S{word}) {
		push @argv, join("", @token);
		@token = ();
		$state = $S{ws};
	    } elsif ($state == $S{s}) {
		push @token, $cc;
		$state = $S{word};
	    } elsif ($state == $S{q}) {
		push @token, $cc;
	    } elsif ($state == $S{qq}) {
		push @token, $cc;
	    } elsif ($state == $S{ws}) {
		# ignore ws between tokens
	    } else {
		die("splitArg error: unknown  state");
		last;
	    }

	} elsif ($cc eq '\'' ) {
	    if      ($state == $S{word}) {
		push @token, $cc;
		$state = $S{q};
	    } elsif ($state == $S{s}) {
		push @token, $cc;
		$state = $S{word};
	    } elsif ($state == $S{q}) {
		push @token, $cc;
		$state = $S{word};
	    } elsif ($state == $S{qq}) {
		push @token, $cc;
	    } elsif ($state == $S{ws}) {
		push @token, $cc;
		$state = $S{q};
	    } else {
		die("splitArg error: unknown  state");
	    }

	} elsif ($cc eq '"' ) {
	    if      ($state == $S{word}) {
		push @token, $cc;
		$state = $S{qq};
	    } elsif ($state == $S{s}) {
		push @token, $cc;
		$state = $S{word};
	    } elsif ($state == $S{q}) {
		push @token, $cc;
	    } elsif ($state == $S{qq}) {
		push @token, $cc;
		$state = $S{word};
	    } elsif ($state == $S{ws}) {
		push @token, $cc;
		$state = $S{qq};
	    } else {
		die("splitArg error: unknown  state");
	    }

	} elsif ($cc eq '\\' ) {
	    if      ($state == $S{word}) {
		$state = $S{s};
	    } elsif ($state == $S{s}) {
		push @token, $cc;
		$state = $S{word};
	    } elsif ($state == $S{q}) {
		push @token, $cc;
	    } elsif ($state == $S{qq}) {
		push @token, $cc;
	    } elsif ($state == $S{ws}) {
		$state = $S{s};
	    } else {
		die("splitArg error: unknown  state");
	    }

	} elsif ($cc =~ m/[[:print:]]/ ) {
	    if      ($state == $S{word}) {
		push @token, $cc;
	    } elsif ($state == $S{s}) {
		push @token, $cc;
		$state = $S{word};
	    } elsif ($state == $S{q}) {
		push @token, $cc;
	    } elsif ($state == $S{qq}) {
		push @token, $cc;
	    } elsif ($state == $S{ws}) {
		push @token, $cc;
		$state = $S{word};
	    } else {
		die("splitArg error: unknown  state");
	    }

	} else {
	    die("splitArg error: unknown input <$_>");

	}

#	print "endstate = $state\n";

    }

    @argv;
}

sub num2bits($) {
    my $num = shift;
    my @bit = ();

    my $pos = 0;
    while ($num) {
	my $bit = ($num & 0x1) << $pos;
	push @bit, $bit if ($bit);
	$num >>= 1;
	$pos++;
    }

    @bit;
}
sub str2int($) {
    my $str = shift;
    my $num = undef;

    if (!defined($str)) { return undef; }
    my $sign = 1;
    if ($str =~ m/^-(.*)/) {
	$str = $1;
	$sign = -1;
    } elsif ($str =~ m/^\+(.*)/) {
	$str = $1;
	$sign = 1;
    }
    $str = lc($str);
    if ($str =~ m/^0x[0-9a-f]+$/) {
	$num = $sign * hex($str);
    } elsif ($str =~ m/^0[0-7]+$/) {
	$num = $sign * oct($str);
    } elsif ($str =~ m/^0b[0-1]+$/) {
	$num = $sign * oct($str);
    } elsif ($str =~ m/^[0-9]+$/) {
	$num = $sign * $str;
    }
    return $num;
}
sub prHex($) {
    my $num = shift;
    my $str = "";
    if (defined($num)) { $str = sprintf("0x%05x", $num); }
    else { $str = "  undef"; }
    $str;
}
sub prStr($) {
    my $str = shift;
    if (!defined($str)) { $str = "  undef"; }
    $str;
}
sub delimitStr($$@) {
    my $A = shift;
    my $B = shift;
    my @str = @_;

    my @arr = ();
    for my $str (@str) {
	push @arr, "$A$str$B";
    }
    @arr;
}

sub basename($) {
    my $path = shift;

    $path =~ s|.*/||;
    $path;
}
sub dirname($) {
    my $path = shift  // "";

    if ($path eq "/") {
	# no op.
    } elsif ($path =~ m|/|) {
	$path =~ s|/[^/]*$||;
    } else {
	$path = ".";
    }
    $path;
}
sub is_same_dir($$) {
    my $a = shift;
    my $b = shift;

    my $ret = 0;
    my ($adev,$aino,@a)	= stat($a);
    my ($bdev,$bino,@b)	= stat($b);
    if ($adev == $bdev && $aino == $bino) {
	$ret = 1;
    }
    $ret;
}

sub show_hash(@) {
    my %data = @_;
    for my $k (sort keys %data) {
	my $v = $data{$k};
	print "$k = $v\n";
    }
}

sub find($$;$) {
    my $dir = shift;
    my $name = shift;
    my $depth = shift // 0;

    my $str;
    if ($depth) {
	$str = `find \"$dir\" -maxdepth $depth -name $name -print0`;
    } else {
	$str = `find \"$dir\" -name $name -print0`;
    }
    my @file = split(/\000/, $str);
    @file;
}

sub RevArr(@) {
    # for many elements with the same walue
    my @arr = @_;
    my %h = ();
    my $ix;

    for (my $ix = 0; $ix < @arr; $ix++) {
	if (!defined($h{$arr[$ix]})) { $h{$arr[$ix]} = []; }
	push @{$h{$arr[$ix]}}, $ix;
    }
    %h;
}
sub RevArrBN(@) {
    # for many elements with the same walue
    my @arr = @_;
    my %h = ();
    my $ix;

    for (my $ix = 0; $ix < @arr; $ix++) {
	my $bn = $arr[$ix];
	$bn =~ s|.*/||;
	if (!defined($h{$bn})) { $h{$bn} = []; }
	push @{$h{$bn}}, $ix;
    }
    %h;
}

sub revArr(@) {
    # when @arr elements are all uniqe, or when we only care about the last set value
    my @arr = @_;
    my %h = ();
    my $ix;

    for (my $ix = 0; $ix < @arr; $ix++) {
	$h{$arr[$ix]} = $ix;
    }
    %h;
}
sub revArrBN(@) {
    # when @arr elements are all uniqe, or when we only care about the last set value
    my @arr = @_;
    my %h = ();
    my $ix;

    for (my $ix = 0; $ix < @arr; $ix++) {
	my $bn = $arr[$ix];
	$bn =~ s|.*/||;
	$h{$bn} = $ix;
    }
    %h;
}
sub BNtoFile($$$) {
    my $arr = shift;
    my $hsh = shift;
    my $bn  = shift;

    my $file = "";
    if (defined($$hsh{$bn})) {
	my $ix = $$hsh{$bn};
	$file = $$arr[$ix];
    }
    $file;
}

sub filedb_insert($$) {
    my $db = shift // [[], {}];
    my $path = shift;

    my ($arr, $hsh) = @$db;
    my ($name, $non_recursive, $recursive) = @$path;


    if (defined($non_recursive) && ref($non_recursive) eq 'ARRAY') {
	for my $dir (@$non_recursive) {
	    push @$arr, find($dir  , $name, 1);
	}
    }
    if (defined($recursive) && ref($recursive) eq 'ARRAY') {
	for my $dir (@$recursive) {
	    push @$arr, find($dir  , $name, 0);
	}
    }
    %$hsh = RevArrBN(@$arr);

    @$db = ( $arr, $hsh );
    $db;
}
sub filedb_BNtoFull($$) {
    my $db = shift;
    my $bn = shift;

    my $arr = $$db[1]{$bn};
    return undef unless defined($arr);
    my $ix = $$arr[$#$arr];
    return undef unless defined($ix);

    my $file = $$db[0][$ix];
    $file;
}

sub read_file($) {
    my $file = shift;
    my @line = ();

    if (open(FH, $file)) {
	@line = <FH>;
	close(FH);
    } else {
	warn("cannot open \"$file\"");
	return ();
    }
    # make sure last line has a \n
    chomp $line[$#line];
    $line[$#line] .= "\n";

    @line;
}
sub write_file($@) {
    my $file = shift;
    my @line = @_;

    if (open(FH, ">", $file)) {
	print FH @line;
	close(FH);
    } else {
	warn("cannot open \"$file\"");
	return 1;
    }
    0;
}

1;
