#!/usr/bin/perl -w
# NAME: showthreejs.pl
# AIM: Read and display contents and information on a three.js file
# 09/01/2015 geoff mclane http://geoffair.net/mperl
use strict;
use warnings;
use File::Basename;  # split path ($name,$dir,$ext) = fileparse($file [, qr/\.[^.]*/] )
use Cwd;
use JSON;
use Data::Dumper;

my $os = $^O;
my $perl_dir = '/home/geoff/bin';
my $PATH_SEP = '/';
my $temp_dir = '/tmp';
if ($os =~ /win/i) {
    $perl_dir = 'C:\GTools\perl';
    $temp_dir = $perl_dir;
    $PATH_SEP = "\\";
}
unshift(@INC, $perl_dir);
require 'lib_utils.pl' or die "Unable to load 'lib_utils.pl' Check paths in \@INC...\n";
# log file stuff
our ($LF);
my $pgmname = $0;
if ($pgmname =~ /(\\|\/)/) {
    my @tmpsp = split(/(\\|\/)/,$pgmname);
    $pgmname = $tmpsp[-1];
}
my $outfile = $temp_dir.$PATH_SEP."temp.$pgmname.txt";
open_log($outfile);

# user variables
my $VERS = "0.0.5 2015-01-09";
my $load_log = 0;
my $in_file = '';
my $verbosity = 0;
my $out_file = '';
my $write_adjusted = 0;
my $show_ints = 0;
my @in_files = ();

# ### DEBUG ###
my $debug_on = 0;
###my $def_file = 'F:\FGx\fgx-aircraft\data\Carreidas\carreidas.js';
my $def_file1 = 'F:\FGx\fgx-aircraft\data\777\777-200.js';
my $def_file2 = 'F:\Projects\aircraft\777\777-200-2.js';
my $def_file3 = 'C:\OSGeo4W\apache\htdocs\map-test2\ac\777-200.js';
my $def_file4 = 'C:\OSGeo4W\apache\htdocs\map-test2\ac';
### program variables
my @warnings = ();
my $cwd = cwd();

sub VERB1() { return $verbosity >= 1; }
sub VERB2() { return $verbosity >= 2; }
sub VERB5() { return $verbosity >= 5; }
sub VERB9() { return $verbosity >= 9; }

sub show_warnings($) {
    my ($val) = @_;
    if (@warnings) {
        prt( "\nGot ".scalar @warnings." WARNINGS...\n" );
        foreach my $itm (@warnings) {
           prt("$itm\n");
        }
        prt("\n");
    } else {
        prt( "\nNo warnings issued.\n\n" ) if (VERB9());
    }
}

sub pgm_exit($$) {
    my ($val,$msg) = @_;
    if (length($msg)) {
        $msg .= "\n" if (!($msg =~ /\n$/));
        prt($msg);
    }
    show_warnings($val);
    close_log($outfile,$load_log);
    exit($val);
}


sub prtw($) {
   my ($tx) = shift;
   $tx =~ s/\n$//;
   prt("$tx\n");
   push(@warnings,$tx);
}

sub isBitSet($$) {
    my ($val, $pos) = @_;
	return ($val & ( 1 << $pos )) ? 1 : 0;
}

sub get_decimal6($) {
    my $n = shift;
    my $num = sprintf("%.6f",($n+0.0000005));
    return $num;
}

sub key_in_array($$$) {
    my ($key,$ra,$ri) = @_;
    my $max = scalar @{$ra};
    my ($i,$txt);
    for ($i = 0; $i < $max; $i++) {
        $txt = ${$ra}[$i];
        if ($key eq $txt) {
            ${$ri} = $i;
            return 1;
        }
    }
    return 0;
}

sub js_head_txt($$$) {
    my ($max,$max2,$name) = @_;
    my $txt = <<EOF;
{
	"metadata" :
	{
		"formatVersion" : 3.1,
		"generatedBy"   : "$name",
		"vertices"      : $max,
		"faces"         : $max2,
		"normals"       : 0,
		"colors"        : 0,
		"uvs"           : [],
		"materials"     : 1,
		"morphTargets"  : 0,
		"bones"         : 0
	},
	"scale" : 1.000000,
	"materials" : [	{
		"DbgColor" : 15658734,
		"DbgIndex" : 0,
		"DbgName" : "Material",
		"blending" : "NormalBlending",
		"colorAmbient" : [0.6400000190734865, 0.6400000190734865, 0.6400000190734865],
		"colorDiffuse" : [0.6400000190734865, 0.6400000190734865, 0.6400000190734865],
		"colorSpecular" : [0.5, 0.5, 0.5],
		"depthTest" : true,
		"depthWrite" : true,
		"shading" : "Lambert",
		"specularCoef" : 50,
		"transparency" : 1.0,
		"transparent" : false,
		"vertexColors" : false
	}],
	"vertices" : [
EOF
    return $txt;
}
sub get_faces_txt() {
    my $txt = <<EOF;
],
	"morphTargets" : [],
	"normals" : [],
	"colors" : [],
	"uvs" : [],
	"faces" : [
EOF
    return $txt;
}

sub get_tail_txt() {
    my $txt = <<EOF;
],
	"bones" : [],
	"skinIndices" : [],
	"skinWeights" : [],
	"animation" : {}
}
EOF
    return $txt;
}

sub writenewjs($$$$) {
    my ($rv,$fa,$inf,$ht) = @_;
    my $js = '';
    my $max  = scalar @{$rv};
    my $max2 = scalar @{$fa};
    my ($i,$i2,$txt,$x,$y,$z,@arr,$cnt);
    for ($i = 0; $i < $max; $i++) {
        $i2 = $i + 1;
        $txt = ${$rv}[$i];
        @arr = split(/\|/,$txt);
        $cnt = scalar @arr;
        if ($cnt == 3) {
            $x = $arr[0];
            $y = $arr[1];
            $z = $arr[2];
            $js .= "$x,$y,$z";
            $js .= ',' if ($i2 < $max);
        } else {
            prt("Write new json FAILED. Split is $cnt, not 3!\n");
            return;
        }
    }
    $js .= get_faces_txt();
    for ($i = 0; $i < $max2; $i++) {
        $i2 = $i + 1;
        $js .= ${$fa}[$i];
        $js .= ',' if ($i2 < $max2);
    }
    my ($name,$dir,$ext) = fileparse($inf, qr/\.[^.]*/);
    $i = 1;
    my $nf = $dir.$name.'-'.$i.$ext;
    while (-f $nf) {
        $i++;
        $nf = $dir.$name.'-'.$i.$ext;
    }
    $js = js_head_txt(($max*3),$max2,$ht) . $js . get_tail_txt();
    write2file($js,$nf);
    prt("Adjusted json written to $nf\n");
}

sub type_bitmask() {
    my $txt = <<EOF;
from : https://github.com/mrdoob/three.js/wiki/JSON-Model-format-3
0: 0 = triangle (3 indices), 1 = quad (4 indices)
1: 0 = no face material, 1 = face material (1 index)
2: 0 = no face uvs, 1 = face uvs (1 index)
3: 0 = no face vertex uvs, 1 = face vertex uvs (3 indices or 4 indices)
4: 0 = no face normal, 1 = face normal (1 index)
5: 0 = no face vertex normals, 1 = face vertex normals (3 indices or 4 indices)
6: 0 = no face color, 1 = face color (1 index)
7: 0 = no face vertex colors, 1 = face vertex colors (3 indices or 4 indices)
EOF
    return $txt;
}

sub mycmp_decend {
   return 1 if ($a < $b);
   return -1 if ($a > $b);
   return 0;
}

sub mycmp_ascend {
   return 1 if ($a > $b);
   return -1 if ($a < $b);
   return 0;
}


sub process_in_file($) {
    my ($inf) = @_;
    if (! open INF, "<$inf") {
        pgm_exit(1,"ERROR: Unable to open file [$inf]\n"); 
    }
    my @lines = <INF>;
    close INF;
    my ($name,$dir,$ext) = fileparse($inf, qr/\.[^.]*/);
    my $lncnt = scalar @lines;
    prt("\nProcessing $lncnt lines, from [$inf]...\n") if (VERB5());
    my $line = join("",@lines);
    my $json = JSON->new->allow_nonref;
    my $rh = $json->decode( $line );
    my ($fa,$va,$tfc,$tvc,$vc,$vc2);
    my ($i,$bit,$fc,$ioff,$x,$y,$z,$sx,$sy,$sz,$ox,$oy,$oz,$oq);
    my ($minx,$miny,$minz,$maxx,$maxy,$maxz);
    my ($ix,$iy,$iz,$key,$ind,$ib,$msg,$i3);
    my ($isQuad,$hasMaterial,$hasFaceUv,$hasFaceVertexUv,$hasFaceNormal,$hasFaceVertexNormal,$hasFaceColor,$hasFaceVertexColor);
    my %dxyz = ();
    my @verts = ();
    my @faces = ();
    my %mapped = ();
    my %indexes = ();
    my $mvc = 0;
    my $dodump = 0;
    my $gotarrays = 0;
    my $remapcnt = 0;
    my $facechg = 0;
    if (defined ${$rh}{faces} && defined ${$rh}{vertices}) {
        $fa = ${$rh}{faces};
        $va = ${$rh}{vertices};
        $gotarrays = 1;
    } elsif (defined ${$rh}{geometries}) {
            #  'geometries' => [ {
            #     'data' => {
            #     'normals' => [ ...,'0.322486194574073','0.446980223599403','0.999241691922136'],
            #     'vertices' => ['1.89533','-0.684473','-11.1331'],
            #     'faces' => [48,0,1,2,0,1,2,3,48,
            my $dra = ${$rh}{geometries};
            my $drh = ${$dra}[0];
            if (defined ${$drh}{data}) {
                my $drh2 = ${$drh}{data};
                if (defined ${$drh2}{faces} && defined ${$drh2}{vertices}) {
                    $fa = ${$drh2}{faces};
                    $va = ${$drh2}{vertices};
                    $gotarrays = 1;
                }
            }
    }
    if (!$gotarrays) {
        if (defined ${$rh}{data}) {
            my $drh2 = ${$rh}{data};
            if (defined ${$drh2}{faces} && defined ${$drh2}{vertices}) {
                $fa = ${$drh2}{faces};
                $va = ${$drh2}{vertices};
                $gotarrays = 1;
            }
        }
    }
    if ($gotarrays) {
        $tfc = scalar @{$fa};
        $tvc = scalar @{$va};
        $vc = $tvc / 3;
        $ioff = 0;
        $x = ${$va}[$ioff + 0];
        $y = ${$va}[$ioff + 1];
        $z = ${$va}[$ioff + 2];
        $minx = $x;
        $maxx = $x;
        $miny = $y;
        $maxy = $y;
        $minz = $z;
        $maxz = $z;
        for ($i = 0; $i < $vc; $i++) {
            $ioff = $i * 3;
            $x = ${$va}[$ioff + 0];
            $y = ${$va}[$ioff + 1];
            $z = ${$va}[$ioff + 2];
            $minx = $x if ($x < $minx);
            $maxx = $x if ($x > $maxx);
            $miny = $y if ($y < $miny);
            $maxy = $y if ($y > $maxy);
            $minz = $z if ($z < $minz);
            $maxz = $z if ($z > $maxz);
            $key = "$x|$y|$z";
            if (defined $dxyz{$key}) {
            ###if (key_in_array($key,\@verts,\$ind)) {
                $ind = $dxyz{$key} - 1;
                $mapped{$i} = $ind;
            } else {
                $dxyz{$key} = $i + 1;
                push(@verts,$key);
            }
        }
        $vc2 = scalar @verts;
        $mvc = scalar keys %dxyz;
        $fc = 0;
        for ($i = 0; $i < $tfc; $i++) {
            $bit = ${$fa}[$i];
            $isQuad              = isBitSet( $bit, 0 );
            $hasMaterial         = isBitSet( $bit, 1 );
            $hasFaceUv           = isBitSet( $bit, 2 );
            $hasFaceVertexUv     = isBitSet( $bit, 3 );
            $hasFaceNormal       = isBitSet( $bit, 4 );
            $hasFaceVertexNormal = isBitSet( $bit, 5 );
            $hasFaceColor	     = isBitSet( $bit, 6 );
            $hasFaceVertexColor  = isBitSet( $bit, 7 );
            $ox = ${$fa}[$i+1];
            $oy = ${$fa}[$i+2];
            $oz = ${$fa}[$i+3];
            if (defined $indexes{$ox}) {
                $indexes{$ox}++;
            } else {
                $indexes{$ox} = 1;
            }
            if (defined $indexes{$oy}) {
                $indexes{$oy}++;
            } else {
                $indexes{$oy} = 1;
            }
            if (defined $indexes{$oz}) {
                $indexes{$oz}++;
            } else {
                $indexes{$oz} = 1;
            }

            $ind = 0;
            if ($ox && defined $mapped{$ox}) {
                ${$fa}[$i+1] = $mapped{$ox};
                $remapcnt++;
                $ind++;
                $ox = $mapped{$ox};
            }
            if ($oy && defined $mapped{$oy}) {
                ${$fa}[$i+2] = $mapped{$oy};
                $remapcnt++;
                $ind++;
                $oy = $mapped{$oy};
            }
            if ($oz && defined $mapped{$oz}) {
                ${$fa}[$i+3] = $mapped{$oz};
                $remapcnt++;
                $ind++;
                $oz = $mapped{$oz};
            }
            if ($ind) {
                $facechg++;
            }
            push(@faces,$bit);
            push(@faces,$ox);
            push(@faces,$oy);
            push(@faces,$oz);
            $msg = "Bit $bit $ox,$oy,$oz";
            $ib = 3;
            $i3 = 0;
            if ($isQuad) {
                $ib++;
                $oq = ${$fa}[$i+$ib];
                $msg .= ",$oq";
                push(@faces,$oq);
                if (defined $indexes{$oq}) {
                    $indexes{$oq}++;
                } else {
                    $indexes{$oq} = 1;
                }
            }
            if ($hasMaterial) {
                $i3++;
                $oq = ${$fa}[$i+$ib+$i3];
                $msg .= ",$oq";
                push(@faces,$oq);
            }
            if ($hasFaceUv) {
                $i3++;
                $oq = ${$fa}[$i+$ib+$i3];
                $msg .= ",$oq";
                push(@faces,$oq);
            }
            if ($hasFaceVertexUv) {
                $oq = ${$fa}[$i+$ib+$i3+1];
                $msg .= ",$oq";
                push(@faces,$oq);
                $oq = ${$fa}[$i+$ib+$i3+2];
                $msg .= ",$oq";
                push(@faces,$oq);
                $oq = ${$fa}[$i+$ib+$i3+3];
                $msg .= ",$oq";
                push(@faces,$oq);
                if ($isQuad) {
                    $oq = ${$fa}[$i+$ib+$i3+3];
                    $msg .= ",$oq";
                    push(@faces,$oq);
                }
                $i3 += $ib;
            }
            if ($hasFaceNormal) {
                $i3++;
                $oq = ${$fa}[$i+$ib+$i3];
                $msg .= ",$oq";
                push(@faces,$oq);
            }
            if ($hasFaceVertexNormal) {
                $oq = ${$fa}[$i+$ib+$i3+1];
                push(@faces,$oq);
                $msg .= ",$oq";
                $oq = ${$fa}[$i+$ib+$i3+2];
                push(@faces,$oq);
                $msg .= ",$oq";
                $oq = ${$fa}[$i+$ib+$i3+3];
                push(@faces,$oq);
                $msg .= ",$oq";
                if ($isQuad) {
                    $oq = ${$fa}[$i+$ib+$i3+3];
                    push(@faces,$oq);
                    $msg .= ",$oq";
                }
                $i3 += $ib;
            }
            if ($hasFaceColor) {
                $i3++;
                $oq = ${$fa}[$i+$ib+$i3];
                push(@faces,$oq);
                $msg .= ",$oq";
            }
            if ($hasFaceVertexColor) {
                $oq = ${$fa}[$i+$ib+$i3+1];
                $msg .= ",$oq";
                push(@faces,$oq);
                $oq = ${$fa}[$i+$ib+$i3+2];
                $msg .= ",$oq";
                push(@faces,$oq);
                $oq = ${$fa}[$i+$ib+$i3+3];
                $msg .= ",$oq";
                push(@faces,$oq);
                if ($isQuad) {
                    $oq = ${$fa}[$i+$ib+$i3+3];
                    push(@faces,$oq);
                    $msg .= ",$oq";
                }
                $i3 += $ib;
            }
            $i += ($ib + $i3);
            prt("$msg\n") if (VERB9());
            $fc++;
        }

        $sx = ($maxx - $minx);
        $sy = ($maxy - $miny);
        $sz = ($maxz - $minz);
        $x  = get_decimal6(($maxx + $minx) / 2);
        $y  = get_decimal6(($maxy + $miny) / 2);
        $z  = get_decimal6(($maxz - $minz) / 2);
        if ($show_ints) {
            $ix = int(($maxx - $minx) + 0.5);
            $iy = int(($maxy - $miny) + 0.5);
            $iz = int(($maxz - $minz) + 0.5);
        } else {
            $ix = get_decimal6($maxx - $minx);
            $iy = get_decimal6($maxy - $miny);
            $iz = get_decimal6($maxz - $minz);
        }
        prt("$name XYZ $ix $iy $iz, Center $x $y $z\n");
        if (length($out_file)) {
            $msg = "[dimensions]\n";
            $msg .= "x = $ix\n";
            $msg .= "y = $iy\n";
            $msg .= "z = $iz\n";
            write2file($msg,$out_file);
            prt("Dimensions written to $out_file\n");
        }
        if (VERB1()) {
            prt("Found $vc vertices ($tvc), mapped to $mvc diff, and $fc faces...\n");
        }
        if (VERB2()) {
            prt("Range xyz mins $minx $miny $minz, maxs $maxx $maxy $maxz\n");
            ###prt("Span XYZ $sx $sy $sz\n");
        }
        if (VERB5()) {
            my @arr = sort mycmp_ascend keys(%indexes);
            $sz = scalar @arr;
            $sx = $arr[0];
            $sy = $arr[$sz-1];
            prt("Total $sz indexes, range $sx to $sy, first ");
            $ioff = $sx * 3;
            if (($ioff + 2) < $tvc) {
                $x = ${$va}[$ioff + 0];
                $y = ${$va}[$ioff + 1];
                $z = ${$va}[$ioff + 2];
                prt("($x,$y,$y) ");
            } else {
                prt("out-of-range ");
            }
            $ioff = $sy * 3;
            prt("last ");
            if (($ioff + 2) < $tvc) {
                $x = ${$va}[$ioff + 0];
                $y = ${$va}[$ioff + 1];
                $z = ${$va}[$ioff + 2];
                prt("($x,$y,$y) ");
            } else {
                prt("out-of-range ");
            }
            prt("\n");
            $msg = "Unused indexes ";
            $sx = 0;
            $ioff = 0;
            for ($sz = 0; $sz < $vc; $sz++) {
                if (defined $indexes{$sz}) {
                    $sy = $indexes{$sz};
                    $ioff = $sy if ($sy > $ioff);
                } else {
                    $msg .= "$sz ";
                    $sx++;
                }
            }
            $msg .= ", cnt $sx, max use $ioff";
            prt("$msg\n");
        }
        if ($remapcnt && $write_adjusted) {
            $line = "$pgmname: version $VERS on ".lu_get_YYYYMMDD_hhmmss(time());
            prt("Remapped $remapcnt offsets in $facechg faces...\n");
            ###writenewjs(\@verts,$fa,$inf,$line);
            writenewjs(\@verts,\@faces,$inf,$line);
        }
    } else {
        $msg = 'geometries NOT FOUND';
        if (defined ${$rh}{geometries}) {
            $msg = "got 'geometries' ";
            my $dra = ${$rh}{geometries};
            my $drh = ${$dra}[0];
            if (defined ${$drh}{data}) {
                $msg .= "got 'data' ";
                my $drh2 = ${$drh}{data};
                if (defined ${$drh2}{faces} && defined ${$drh2}{vertices}) {
                    $msg .= "got 'faces' and 'vertices' ";
                }
            }
            prt("TODO: geometries... $msg\n");
        } else {
            $dodump = 1;
        }

    }

    if ($dodump) {
        prt("'faces' or 'vertices' NOT defined\n");
        prt(Dumper($rh));
        $load_log = 1;
    }
}

sub my_file_type($) {
    my $file = shift;
    return 1 if ($file =~ /\.js$/i);
    return 0;
}

sub process_in_dir($) {
    my $dir = shift;
    if (!opendir(DIR,$dir)) {
        prtw("WARNING: Failed to open directory $dir!\n");
        return;
    }
    my @files = readdir(DIR);
    closedir(DIR);
    my  ($file,$ff);
    my @dirs = ();
    ut_fix_directory(\$dir);
    foreach $file (@files) {
        next if ($file eq '.');
        next if ($file eq '..');
        $ff = $dir.$file;
        if (-f $ff) {
            process_in_file($ff) if (my_file_type($file));
        } elsif (-d $ff) {
            push(@dirs,$ff);
        } else {
            pgm_exit(1,"What is this? $ff ($file)! *** FIX ME ***\n");
        }
    }
}

sub process_in_files() {
    foreach $in_file (@in_files) {
        if (-f $in_file) {
            process_in_file($in_file);
        } elsif (-d $in_file) {
            process_in_dir($in_file);
        }
    }
}

#########################################
### MAIN ###
parse_args(@ARGV);
process_in_files();
pgm_exit(0,"");
########################################

sub need_arg {
    my ($arg,@av) = @_;
    pgm_exit(1,"ERROR: [$arg] must have a following argument!\n") if (!@av);
}

sub parse_args {
    my (@av) = @_;
    my ($arg,$sarg);
    my $verb = VERB2();
    while (@av) {
        $arg = $av[0];
        if ($arg =~ /^-/) {
            $sarg = substr($arg,1);
            $sarg = substr($sarg,1) while ($sarg =~ /^-/);
            if (($sarg =~ /^h/i)||($sarg eq '?')) {
                give_help();
                pgm_exit(0,"Help exit(0)");
            } elsif ($sarg =~ /^v/) {
                if ($sarg =~ /^v.*(\d+)$/) {
                    $verbosity = $1;
                } else {
                    while ($sarg =~ /^v/) {
                        $verbosity++;
                        $sarg = substr($sarg,1);
                    }
                }
                $verb = VERB2();
                prt("Verbosity = $verbosity\n") if ($verb);
            } elsif ($sarg =~ /^l/) {
                if ($sarg =~ /^ll/) {
                    $load_log = 2;
                } else {
                    $load_log = 1;
                }
                prt("Set to load log at end. ($load_log)\n") if ($verb);
            } elsif ($sarg =~ /^o/) {
                need_arg(@av);
                shift @av;
                $sarg = $av[0];
                $out_file = $sarg;
                prt("Set out file to [$out_file].\n") if ($verb);
            } else {
                pgm_exit(1,"ERROR: Invalid argument [$arg]! Try -?\n");
            }
        } else {
            $in_file = $arg;
            push(@in_files,$in_file);
            if ( -f $in_file) {
                prt("Set input file to [$in_file]\n") if ($verb);
            } elsif ( -d $in_file ) {
                prt("Set input directory to [$in_file]\n") if ($verb);
            } else {
                pgm_exit(1,"ERROR: Unable to find file or directory [$in_file]! Check name, location...\n");
            }
        }
        shift @av;
    }

    if ($debug_on) {
        prtw("WARNING: DEBUG is ON!\n");
        if (length($in_file) ==  0) {
            #$in_file = $def_file1;
            #push(@in_files,$in_file);
            #$in_file = $def_file2;
            #push(@in_files,$in_file);
            #$in_file = $def_file3;
            #push(@in_files,$in_file);
            $in_file = $def_file4;
            push(@in_files,$in_file);
            prt("Set DEFAULT input to [$in_file]\n");
            # $load_log = 1;
        }
    }
    if (length($in_file) ==  0) {
        pgm_exit(1,"ERROR: No input files found in command!\n");
    }
    #if (! -f $in_file) {
    #    pgm_exit(1,"ERROR: Unable to find in file [$in_file]! Check name, location...\n");
    #}
}

sub give_help {
    prt("$pgmname: version $VERS\n");
    prt("Usage: $pgmname [options] in-file\n");
    prt("Options:\n");
    prt(" --help  (-h or -?) = This help, and exit 0.\n");
    prt(" --verb[n]     (-v) = Bump [or set] verbosity. def=$verbosity\n");
    prt(" --load        (-l) = Load LOG at end. ($outfile)\n");
    prt(" --out <file>  (-o) = Write output to this file.\n");
}

# eof - template.pl
