#!/usr/bin/perl -w
# NAME: showsln.pl
# AIM: Read a MSVC solution file, and details the project
# 18/12/2013 - Adjust what is shown by 'verbosity'
# 20/05/2013 - Works well ;=)) Time for some improvemments
# 17/05/2013 geoff mclane http://geoffair.net/mperl
use strict;
use warnings;
use File::Basename;  # split path ($name,$dir,$ext) = fileparse($file [, qr/\.[^.]*/] )
use File::Spec; # File::Spec->rel2abs($rel); # we are IN the SLN directory, get ABSOLUTE from RELATIVE
use XML::Simple;
use Data::Dumper;
use Cwd;
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.1 2013-03-17";
my $load_log = 0;
my $in_file = '';
my $verbosity = 0;
my $out_file = '';
my $del_opt_libs = 1;
my $del_GL_libs = 1;
my $del_dbg_libs = 1;
my $del_special_cmake = 1;
my $del_optional_defines = 1;
my $rel_dir = '';

# ### DEBUG ###
my $debug_on = 0;
my $def_file = 'C:\FG\18\fltk-1.3.2\ide\VisualC2010\fltk.sln';
##my $def_file = 'C:\FG\18\fltk-1.3.2\ide\VisualC2010\adjuster.vcxproj';
my $dbg_sl_01 = 0;
my $dbg_sl_02 = 0;
my $dbg_sl_03 = 0;
my $dbg_sl_14 = 0;
my $dbg_sl_15 = 0;
my $dbg_sl_16 = 0;

### program variables
my @warnings = ();
my $cwd = cwd();
my ($g_name,$g_sln_path); # get the NAME, and SOLUTION PATH (should be ABSOLUTE, NOT relative)

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 is_sln_ext($) {
    my ($fil) = shift;
    my ($nm, $dir, $ext) = fileparse( $fil, qr/\.[^.]*/ );
    my $lce = lc($ext);
    if ($lce eq '.sln') {
        return 1;
    }
    return 0;
}

sub mycmp_nc_sort {
   return -1 if (lc($a) lt lc($b));
   return 1 if (lc($a) gt lc($b));
   return 0;
}
sub mycmp_nc_fn_only {
    my ($f1,$d1,$e1) = fileparse($a, qr/\.[^.]*/);
    my ($f2,$d2,$e2) = fileparse($b, qr/\.[^.]*/);
    return -1 if (lc($f1) lt lc($f2));
    return  1 if (lc($f1) gt lc($f2));
    return 0;
}

sub is_vcproj_ext($) {
    my ($fil) = shift;
    my ($nm, $dir, $ext) = fileparse( $fil, qr/\.[^.]*/ );
    my $lce = lc($ext);
    return 1 if ($lce eq '.vcproj');
    return 0;
}

sub is_vcxproj_ext($) {
    my $fil = shift;
    my ($nm, $dir, $ext) = fileparse( $fil, qr/\.[^.]*/ );
    my $lce = lc($ext);
    return 1 if ($lce eq '.vcxproj');   # 24/11/2010 VC10 support
    return 0;
}

my %Level_1 = (
      'ItemGroup' => 2,
      'xmlns' => 1,
      'Import' => 2,
      'ImportGroup' => 2,
      'PropertyGroup' => 2,
      'DefaultTargets' => 1,
      'ToolsVersion' => 1,
      'ItemDefinitionGroup' => 2
      );
my %ItemGourp = (
    'ProjectConfiguration' => 2,
    'ClCompile' => 2, # arr of hash 'Include' => '..\\..\\test\\CubeMain.cxx'
    'CustomBuild' => 2,
    'ProjectReference' => 2
    );

my %ItemDefinitionGroup = (
    'Midl' => 3, # 'HeaderFileName' => {}, 'TargetEnvironment' => 'Win32', 'TypeLibraryName' => '.\\CubeView__0/CubeView.tlb',
        # 'PreprocessorDefinitions' => '_DEBUG;%(PreprocessorDefinitions)'
    'ResourceCompile' => 3, # 'Culture' => '0x0409', 'PreprocessorDefinitions' => '_DEBUG;%(PreprocessorDefinitions)'
    'Condition' => 1, #'\'$(Configuration)|$(Platform)\'==\'Debug|Win32\'',
    'Link' => 3,
    #'ProgramDatabaseFile' => '$(IntDir)$(TargetName).pdb',
    # 'GenerateDebugInformation' => 'true',
    # 'AdditionalDependencies' => 'opengl32.lib;comctl32.lib;%(AdditionalDependencies)',
    # 'TargetMachine' => 'MachineX86',
    # 'IgnoreSpecificDefaultLibraries' => 'libcd;%(IgnoreSpecificDefaultLibraries)',
    # 'SubSystem' => 'Windows',
    # 'DataExecutionPrevention' => {},
    # 'RandomizedBaseAddress' => 'false',
    # 'SuppressStartupBanner' => 'true',
    #  'OutputFile' => '../../test/CubeViewd.exe',
    # 'AdditionalLibraryDirectories' => '..\\..\\lib;%(AdditionalLibraryDirectories)'
    'ClCompile' => 3
    # 'Optimization' => 'Disabled',
    # 'CompileAs' => 'Default',
    # 'DebugInformationFormat' => 'ProgramDatabase',
    # 'SuppressStartupBanner' => 'true',
    # 'PrecompiledHeader' => {},
    # 'RuntimeLibrary' => 'MultiThreadedDebugDLL',
    #'PreprocessorDefinitions' => '_CRT_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_WINDOWS;WIN32_LEAN_AND_MEAN;VC_EXTRA_LEAN;WIN32_EXTRA_LEAN;%(PreprocessorDefinitions)',
    # 'AdditionalIncludeDirectories' => '.;..\\..\\zlib;..\\..\\png;..\\..\\jpeg;../..;%(AdditionalIncludeDirectories)'
);

my %master_hash = ();   # collect ALL information into here

sub process_xml_file($$) {
    my ($inf,$proj) = @_;
    if (! -f $inf ) {
        pgm_exit(1,"ERROR: Unable to find file [$inf]\n"); 
    }
    my $xml = new XML::Simple; # (ForceArray => 0);
    my $data = $xml->XMLin($inf);
    ##prt(Dumper($data));
    ##$load_log = 2;
    my ($key,$val,$typ,$acnt,$rt,$righ,@arr,$item,$tmp);
    my ($rcmpa);
    my ($rca,$src);
    my ($rpph,$ppc,$rppa,$ff);
    my %SOURCES = ();
    my %FF_SRCS = ();
    my @a = ();
    my %pp = ();
    my %aid = ();
    my %ald = ();
    my %adl = ();
    my $config = '';
    my $subsystem = '';
    $ff = File::Spec->rel2abs($inf);
    my ($inam,$idir) = fileparse($ff);
    foreach $key (keys %Level_1) {
        $typ = $Level_1{$key};
        if (defined ${$data}{$key}) {
            $val = ${$data}{$key};
            $rt = ref($val);
            if ($typ == 1) {
                prt("$key = $val\n") if (VERB9());
            } elsif ($typ == 2) {
                # this is an ARRAY
                if ($rt eq 'ARRAY') {
                    $acnt = scalar @{$val};
                    prt("$key is arrays with $acnt members...\n") if (VERB9());
                    if ($key eq 'ItemGroup') {
                        # this is where we SHOULD find the SOURCES
                        foreach $righ (@{$val}) {
                            if (defined ${$righ}{'ClCompile'}) {
                                $rcmpa = ${$righ}{'ClCompile'};
                                $rt = ref($rcmpa);
                                if ($rt eq 'ARRAY') {
                                    foreach $rca (@{$rcmpa}) {
                                        $item = 'Include';
                                        if (defined ${$rca}{$item}) {
                                            $src = ${$rca}{$item};
                                            $SOURCES{$src} = 1;
                                            $ff = File::Spec->rel2abs($idir.$src);
                                            $FF_SRCS{$ff} = 1;
                                        }
                                        $item = 'PreprocessorDefinitions';
                                        if (defined ${$rca}{$item}) {
                                            $rppa = ${$rca}{$item};
                                            foreach $rpph (@{$rppa}) {
                                                if (defined ${$rpph}{'content'}) {
                                                    $ppc = ${$rpph}{'content'};
                                                    $ppc =~ s/\%\(PreprocessorDefinitions\)//;
                                                    $pp{$ppc} = 1 if (length($ppc));
                                                }
                                            }
                                        }
                                        $item = 'AdditionalIncludeDirectories';
                                        if (defined ${$rca}{$item}) {
                                            $rppa = ${$rca}{$item};
                                            foreach $rpph (@{$rppa}) {
                                                if (defined ${$rpph}{'content'}) {
                                                    $ppc = ${$rpph}{'content'};
                                                    $ppc =~ s/\%\(AdditionalIncludeDirectories\)//;
                                                    $aid{$ppc} = 1 if (length($ppc));
                                                }
                                            }
                                        }
                                    }
                                } elsif ($rt eq 'HASH') {
                                    $rca = $rcmpa;
                                    $item = 'Include';
                                    if (defined ${$rca}{$item}) {
                                        $src = ${$rca}{$item};
                                        $SOURCES{$src} = 1;
                                        $ff = File::Spec->rel2abs($idir.$src);
                                        $FF_SRCS{$ff} = 1;
                                    }
                                    $item = 'PreprocessorDefinitions';
                                    if (defined ${$rca}{$item}) {
                                        $rppa = ${$rca}{$item};
                                        foreach $rpph (@{$rppa}) {
                                            if (defined ${$rpph}{'content'}) {
                                                $ppc = ${$rpph}{'content'};
                                                $ppc =~ s/\%\(PreprocessorDefinitions\)//;
                                                $pp{$ppc} = 1 if (length($ppc));
                                            }
                                        }
                                    }
                                    $item = 'AdditionalIncludeDirectories';
                                    if (defined ${$rca}{$item}) {
                                        $rppa = ${$rca}{$item};
                                        foreach $rpph (@{$rppa}) {
                                            if (defined ${$rpph}{'content'}) {
                                                $ppc = ${$rpph}{'content'};
                                                $ppc =~ s/\%\(AdditionalIncludeDirectories\)//;
                                                $aid{$ppc} = 1 if (length($ppc));
                                            }
                                        }
                                    }

                                }
                            }
                        }
                    } elsif ($key eq 'ItemDefinitionGroup') {
                        foreach $righ (@{$val}) {
                            if (defined ${$righ}{'Link'}) {
                                $rcmpa = ${$righ}{'Link'};
                                $rt = ref($rcmpa);
                                if ($rt eq 'HASH') {
                                    foreach $rca (keys %{$rcmpa}) {
                                        $ppc = ${$rcmpa}{$rca};
                                        if ($rca eq 'AdditionalDependencies') {
                                            $ppc =~ s/\%\(AdditionalDependencies\)//;
                                            @a = split(';',$ppc);
                                            foreach $ppc (@a) {
                                                $adl{$ppc} = 1 if (length($ppc));
                                            }
                                        } elsif ($rca eq 'AdditionalLibraryDirectories') {
                                            $ppc =~ s/\%\(AdditionalLibraryDirectories\)//;
                                            @a = split(';',$ppc);
                                            foreach $ppc (@a) {
                                                $ald{$ppc} = 1 if (length($ppc));
                                            }
                                        } elsif ($rca eq 'SubSystem') {
                                            $subsystem = $ppc if (length($ppc));
                                        }
                                    }
                                }
                            } elsif (defined ${$righ}{'ClCompile'}) {
                                $rcmpa = ${$righ}{'ClCompile'};
                                $rt = ref($rcmpa);
                                if ($rt eq 'ARRAY') {
                                    foreach $rca (@{$rcmpa}) {
                                        $item = 'PreprocessorDefinitions';
                                        if (defined ${$rca}{$item}) {
                                            $rppa = ${$rca}{$item};
                                            foreach $rpph (@{$rppa}) {
                                                if (defined ${$rpph}{'content'}) {
                                                    $ppc = ${$rpph}{'content'};
                                                    $ppc =~ s/\%\(PreprocessorDefinitions\)//;
                                                    $pp{$ppc} = 1 if (length($ppc));
                                                }
                                            }
                                        }
                                        $item = 'AdditionalIncludeDirectories';
                                        if (defined ${$rca}{$item}) {
                                            $rppa = ${$rca}{$item};
                                            foreach $rpph (@{$rppa}) {
                                                if (defined ${$rpph}{'content'}) {
                                                    $ppc = ${$rpph}{'content'};
                                                    $ppc =~ s/\%\(AdditionalIncludeDirectories\)//;
                                                    $aid{$ppc} = 1 if (length($ppc));
                                                }
                                            }
                                        }
                                    }
                                } elsif ($rt eq 'HASH') {
                                    $rca = $rcmpa;
                                    $item = 'PreprocessorDefinitions';
                                    if (defined ${$rca}{$item}) {
                                        $rppa = ${$rca}{$item};
                                        $rt = ref($rppa);
                                        if ($rt eq 'ARRAY') {
                                            foreach $rpph (@{$rppa}) {
                                                if (defined ${$rpph}{'content'}) {
                                                    $ppc = ${$rpph}{'content'};
                                                    $ppc =~ s/\%\(PreprocessorDefinitions\)//;
                                                    $pp{$ppc} = 1 if (length($ppc));
                                                }
                                            }
                                        } elsif ($rt eq 'HASH') {
                                            $rpph = $rppa;
                                            if (defined ${$rpph}{'content'}) {
                                                $ppc = ${$rpph}{'content'};
                                                $ppc =~ s/\%\(PreprocessorDefinitions\)//;
                                                $pp{$ppc} = 1 if (length($ppc));
                                            }
                                        } elsif ($rt eq '') {
                                            $ppc = $rppa;
                                            $ppc =~ s/\%\(PreprocessorDefinitions\)//;
                                            $pp{$ppc} = 1 if (length($ppc));
                                        }
                                    }
                                    $item = 'AdditionalIncludeDirectories';
                                    if (defined ${$rca}{$item}) {
                                        $rppa = ${$rca}{$item};
                                        $rt = ref($rppa);
                                        if ($rt eq 'ARRAY') {
                                            foreach $rpph (@{$rppa}) {
                                                if (defined ${$rpph}{'content'}) {
                                                    $ppc = ${$rpph}{'content'};
                                                    $ppc =~ s/\%\(AdditionalIncludeDirectories\)//;
                                                    $aid{$ppc} = 1 if (length($ppc));
                                                }
                                            }
                                        } elsif ($rt eq 'HASH') {
                                            $rpph = $rppa;
                                            if (defined ${$rpph}{'content'}) {
                                                $ppc = ${$rpph}{'content'};
                                                $ppc =~ s/\%\(AdditionalIncludeDirectories\)//;
                                                $aid{$ppc} = 1 if (length($ppc));
                                            }
                                        } elsif ($rt eq '') {
                                            $ppc = $rppa;
                                            $ppc =~ s/\%\(AdditionalIncludeDirectories\)//;
                                            $aid{$ppc} = 1 if (length($ppc));
                                        }
                                    }
                                }
                            }
                        }
                    } elsif ($key eq 'PropertyGroup') {
                        $rt = ref($val);
                        if ($rt eq 'ARRAY') {
                            foreach $righ (@{$val}) {
                                $rt = ref($righ);
                                if ($rt eq 'HASH') {
                                    # Application, DynamicLibrary, StaticLibrary
                                    $item = 'ConfigurationType';
                                    if (defined ${$righ}{$item}) {
                                        $ppc = ${$righ}{$item};
                                        if (length($config)) {
                                            @a = split(';',$config);
                                            foreach $src (@a) {
                                                if ($src eq $ppc) {
                                                    $ppc = '';
                                                    last;
                                                }
                                            }
                                            $config .= ";$ppc" if (length($ppc));
                                        } else {
                                            $config = $ppc if (length($ppc));
                                        }
                                    }
                                }

                            }
                        }
                    }
                } else {
                    prt("$key: expected array, but got [$rt]\n");
                }
            }
        } else {
            prt("Key $key not found in xml!\n");
        }
    }
    my $splproj = 0;
    # =====================================================================
    # show what was found in this file
    # A CONSOLE APP SHOWS: AppType: Application;Console
    # A WINDOWS APP SHOWS: AppType: Application;Windows
    # A SHARED  DLL SHOWS: AppType: DynamicLibrary;Windows
    # A STATIC  LIB SHOWS: AppType: StaticLibrary;
    if (($proj eq 'ALL_BUILD')||($proj eq 'INSTALL')||($proj eq 'ZERO_CHECK')) {
        prt("$proj: AppType: $config;$subsystem\n") if (VERB9()); # if (length($config)||length($subsystem));
        $splproj = 1;
    } else {
        prt("$proj: AppType: $config;$subsystem\n") if (VERB1()); # if (length($config)||length($subsystem));
    }
    $master_hash{$proj}->{apptype} = "$config;$subsystem";
    my ($relpath,%bs);
    my ($n,$d,$ext);
    # ===================================================================
    # DEAL with the SOURCES if any
    ##############################
    ###@a = sort mycmp_nc_fn_only keys(%SOURCES);
    @a = sort mycmp_nc_fn_only keys(%FF_SRCS);
    if (length($rel_dir)) {
        # need to get relative path to this directory
        %bs = ();
        foreach $key (@a) {
            ($n,$d) = fileparse($key);
            $relpath = get_rel_dos_path($d,$rel_dir);
            $key = $relpath.$n;
            prt("$key\n") if (VERB9());
            $bs{$key} = 1;
        }
        ### @a = sort mycmp_nc_fn_only keys(%bs);
        @a = sort mycmp_nc_sort keys(%bs);
    }
    $master_hash{$proj}->{sources} = [@a];
    $tmp = scalar @a;
    if ($tmp) {
        prt("$proj: $tmp Sources:\n") if (VERB1());
        prt(join("\n",@a)."\n") if (VERB5());
    } elsif (!$splproj) {
        prt("$proj: No sources found.\n") if (VERB5());
    }

    # ====================================================================
    # deal with PREPROCESSOR instruction - try to reduce to minimum
    ####################################
    # drop known items
    my %drop_outs = (
        '_WINDOWS' => 1,
        '_DEBUG'   => 1, 
        'NDEBUG'   => 1,
        'WIN32_LEAN_AND_MEAN' => 1,
        'WIN32' => 1,
        'WIN32_EXTRA_LEAN' => 1,
        'VC_EXTRA_LEAN' => 1,
        '_CRT_SECURE_NO_DEPRECATE' => 1,
        '__CRT_NONSTDC_NO_WARNINGS' => 1,
        '_SCL_SECURE_NO_WARNINGS' => 1,
        '_CRT_SECURE_NO_WARNINGS' => 1 );

    my %drop_optional = (
        '_REENTRANT' => 1,          # also maybe optional
        '_USE_MATH_DEFINES' => 1,   # maybe should be optional
        'NOMINMAX' => 1 );          

    @a = keys %pp;
    %pp = ();
    foreach $key (@a) {
        $key = trim_all($key);
        @arr = split(";",$key);
        foreach $key (@arr) {
            $key = trim_all($key);
            next if (defined $drop_outs{$key});
            if (length($key)) {
                if ($del_special_cmake) {
                    next if ($key =~ /^CMAKE_INTDIR=/);
                }
                if ($del_optional_defines) {
                    next if (defined $drop_optional{$key});
                }
                $pp{$key} = 1;
            }
        }
    }
    @a = sort keys %pp;
    $master_hash{$proj}->{preprocs} = [@a];
    if (@a) {
        prt("$proj: PreProcs: ".join(";",@a)."\n") if (VERB5());
    }
    # ====================================================================


    # ====================================================================
    # LIKEWISE - remove all duplicates, sort, and ensure DOS
    ##################################
    @a = keys %aid;
    %aid = ();
    foreach $key (@a) {
        $key = trim_all($key);
        next if (length($key) == 0);
        @arr = split(";",$key);
        foreach $key (@arr) {
            $key = trim_all($key);
            if (length($key)) {
                $key = path_u2d($key);
                $aid{$key} = 1;
            }
        }
    }
    @a = sort mycmp_nc_sort keys(%aid);
    if (length($rel_dir)) {
        # need to get relative path to this directory
        %bs = ();
        foreach $key (@a) {
            $relpath = get_rel_dos_path($key,$rel_dir);
            prt("$relpath\n") if (VERB9());
            $bs{$relpath} = 1;
        }
        ###@a = sort mycmp_nc_fn_only keys(%bs);
        @a = sort mycmp_nc_sort keys(%bs);
    }

    $master_hash{$proj}->{addincs} = [@a];
    if (@a) {
        prt("$proj: AddIncs: ".join(";",@a)."\n") if (VERB2());
    }
    # ====================================================================

    
    # ====================================================================
    # would like to DROP all standard windows libraries 
    my %win_libraries = (
        'ole32.lib' => 1,
        'shell32.lib' => 1,
        'uuid.lib' => 1,
        'kernel32.lib' => 1,
        'oleaut32.lib' => 1,
        'user32.lib' => 1,
        'winspool.lib' => 1,
        'gdi32.lib' => 1 );
    my %opt_libraries = (
        'advapi32.lib' => 1,
        'comdlg32.lib' => 1,
        'winmm.lib' => 1 );    # maybe not this????
    my %gl_libraries = (
        'opengl32.lib' => 1,
        'glu32.lib' => 1 );

    # AND maybe drop a library used twice where the second is just with a 'd'
    # appended, and maybe optionally remove all paths, and add any path
    # to the additional library directories
    @a = keys %adl;
    %adl = ();
    foreach $key (@a) {
        $key = trim_all($key);
        next if (length($key) == 0);
        @arr = split(/\s+/,$key);
        foreach $key (@arr) {
            $key = trim_all($key);
            if (length($key)) {
                $key = path_u2d($key);
                ($key,$d) = fileparse($key); # get the NAME only
                next if (length($key) == 0);
                $src = lc($key);
                next if (defined $win_libraries{$src});
                if ($del_opt_libs) {
                    next if (defined $opt_libraries{$src});
                }
                if ($del_GL_libs) {
                    next if (defined $gl_libraries{$src});
                }
                $adl{$key} = 1;
            }
        }
    }
    if ($del_dbg_libs) {
        @a = keys %adl;
        @arr = ();
        foreach $key (@a) {
            ($n,$d,$ext) = fileparse($key, qr/\.[^.]*/);
            if ((length($n) > 2) && ($n =~ /d$/i)) {
                $n =~ s/d$//i;   # remove trailing 'd'
                $src = $n.$ext;  # get name without 'd'
                next if (defined $adl{$src}); # skip if we have that name
            }
            push(@arr,$key);
        }
        %adl = ();
        foreach $key (@arr) {
            next if (length($key) == 0);
            $adl{$key} = 1;
        }
    }
    @a = sort mycmp_nc_sort keys(%adl);
    $master_hash{$proj}->{libdeps} = [@a];
    if (@a) {
         if (VERB9()) {
            prt("$proj: LibDeps: ".join(" ",@a)."\n");
         } elsif (VERB2()) {
             my %h = ();
             foreach $key (@a) {
                 $h{$key} = 1;
             }
             @a = sort mycmp_nc_sort keys(%h);
             prt("$proj: LibDeps: ".join(" ",@a)."\n");
         }
    }
    # ====================================================================

    # ====================================================================
    # Additional LIBRARY directories
    @a = keys %ald;
    %ald = ();
    foreach $key (@a) {
        $key = trim_all($key);
        next if (length($key) == 0);
        @arr = split(";",$key);
        foreach $key (@arr) {
            $key = trim_all($key);
            if (length($key)) {
                $key = path_u2d($key);
                $ald{$key} = 1;
            }
        }
    }
    @a = sort mycmp_nc_sort keys(%ald);
    $master_hash{$proj}->{libdirs} = [@a];
    if (@a) {
        prt("$proj: LibDirs: ".join(";",@a)."\n") if (VERB2());
    }
    ###pgm_exit(1,"");
}

sub fix_rel_path2($$) {
    my ($root,$rel) = @_;
    my $cd = cwd();
    my ($fp,$msg,$tmp);
    if (chdir($root)) {
        $fp = File::Spec->rel2abs($rel);    # we are IN the SLN directory, get ABSOLUTE from RELATIVE
        chdir($cd); # and get us back to where we were...
        $msg = "1:File::Spec:";
    } else {
        $fp = fix_rel_path3($root.$rel,'fix_rel_path2'); # else use internal service
        $msg = "2:fix_rel_path3:"
    }
    if (-f $fp) {
        $msg .= " ok";
    } else {
        $msg .= " NOT FOUND";
        if ($fp =~ /\\SimGear\.cs\\/) {
            $tmp = $fp;
            $tmp =~ s/\\SimGear\.cs\\/\\SimGear-cs\\/;
            if (-f $tmp) {
                $fp = $tmp;
                $msg = "ok $msg";
            }
        }
        if ($msg =~ /^1/) {
            $tmp = fix_rel_path3($root.$rel,'fix_rel_path2'); # else use internal service
            if (-f $tmp) {
                $fp = $tmp;
                $msg .= ", but OK with fix_rel_path3!!!";
            } else {
                $msg .= ", NOR from fix_rel_path3 [$tmp]"
            }
        }
    }
    prt("From: [$root] [$rel], got [$fp] $msg\n") if ($dbg_sl_14);
    return $fp; # hopefully, return ABSOLUTE path
}

# Read and store contents of SOLUTION (.sln) file
# 22/04/2008 - Extract DEPENDENCIES from solution file, and add to DSW output
# 24/11/2010 - Support for VC10 XML files
sub process_SLN_file2($) {
	my ($sln_fil_in) = shift;
	my ($cnt, $line, $vers, @arr, $mver, $par, $ff, $itmnum);
	my ($projname, $projfile, $projff, $gotproj, $relpath);
	my ($tnm,$tpth);
	my ($inproj, $tline, $projid, $inpdeps, $projdeps);
    my ($nmdeps, $depid, $pn, $fnd, $list);
    my ($msg,$text,$dspfile,$fdspfil,$name);
    my $fil = File::Spec->rel2abs($sln_fil_in);
	open IF, "<$fil" or mydie( "ERROR: Unable to open [$fil]... $! ...\n" );
	my @lines = <IF>;
	close IF;
	$cnt = scalar @lines;
	($g_name,$g_sln_path) = fileparse($fil); # get the NAME, and SOLUTION PATH (should be ABSOLUTE, NOT relative)
    my %hash = ();
    my %sln_projects = ();
    my %sln_projpath = ();
    my %sln_depends = ();
    my %sln_projids = ();
    my %missed_vcprojs = ();
	prt( "\nProcessing $cnt lines ... n=[$g_name] p=[$g_sln_path] ...\n" ) if (VERB2());
	$projname = '';
	$projfile = '';
	$projff = '';
	$gotproj = 0;
	$inproj = 0;
	$inpdeps = 0;
	foreach $line (@lines) {
		$tline = trim_all($line);
		if ($line =~ /.+Format\s+Version\s+(\d+\.\d+)$/i) {
			$vers = $1;	# get n.nn version
			@arr = split(/\./,$vers);
			$mver = $arr[0];
			prt( "Is MSVC Version $mver ...\n" );
		} elsif ($line =~ /^Project\s*\(/) {
			# seek like 
            #Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ALL_BUILD", "ALL_BUILD.vcxproj", "{4BB0374F-3B6E-4EC8-9BE0-4BE8947B80E3}"
			#Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "abyss", "abyss.vcproj", "{8B384B8A-2B72-4DC4-8DF1-E3EF32F18850}"
            #Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fgadmin", "fgadmin\fgadmin.vcxproj", "{7004E589-7EA0-4AFD-B432-3D5E00B55049}"
            #	ProjectSection(ProjectDependencies) = postProject
            #		{22540CD3-D3CA-4C86-A773-80AEEE3ACDED} = {22540CD3-D3CA-4C86-A773-80AEEE3ACDED}
            #	EndProjectSection
            #EndProject
			###prt( "Got project [$line] ...\n" );
			$inproj = 1;
			@arr = split( '=', $line );
			$cnt = scalar @arr;
			if ($cnt == 2) {
				$par = $arr[1]; # get 2nd part, like say '"abyss", "abyss.vcproj", "{8B384B8A-2B72-4DC4-8DF1-E3EF32F18850}"'
				@arr = split(',', $par);
				$cnt = scalar @arr;
				if ($cnt == 3) {
					$projname = strip_quotes(trim_all($arr[0])); # project NAME
					$projfile = strip_quotes(trim_all($arr[1])); # vcproj FILE
					$projid   = strip_quotes(trim_all($arr[2])); # project ID
					$projff   = fix_rel_path2($g_sln_path,$projfile); # return ABSOLUTE
					# if ((length($projname)) && (is_vcproj_ext($projfile)) && (-f $projff)) {
                    # 01/12/2010 - Remove need for the file to EXIST
					#if ((length($projname)) && (is_vcproj_ext($projfile)) ) {
					if ((length($projname)) && (is_vcproj_ext($projfile) || is_vcxproj_ext($projfile)) ) {
                        if (-f $projff) {
                            # and file
                        } else {
                            $missed_vcprojs{$projname} = $projff;
                        }
						$gotproj = 1;
						($tnm,$tpth,$text) = fileparse($projff,qr/\.[^.]*/);
                        $fdspfil = $tpth.$tnm.".dsp";   # this is a DSP EQUIVALENT to the vcproj location
                        # BUT, we may have been given a DIFFERENT DSP output dir through
                        # -dsp=<dir> ($out_dsp_dir) and $g_had_dsp, and maybe '-fix-rel' ($fix_rel_paths)
                        #if ($g_had_dsp) {
                        #    $fdspfil = $out_dsp_dir;
                        #    $fdspfil .= "\\" if ( !($fdspfil =~ /(\\|\/)$/) );
                        #   $fdspfil .= $tnm.".dsp";
                        #}
						$relpath = get_rel_dos_path($tpth, $g_sln_path);
						($tnm,$tpth,$text) = fileparse($projfile,qr/\.[^.]*/);
                        $dspfile = $tpth.$tnm.".dsp";
						prt( "Got PROJECT name=$projname, file=[$projfile], ff=[$projff], rel=[$relpath].\n" ) if ($dbg_sl_01);
						if (defined $sln_projects{$projname}) {
							mydie( "A PROBLEM: Already GOT this project name $projname!!!\n" );
						} else {
							$sln_projects{$projname} = $projff;
							# $sln_projpath{$projname} = $relpath; # can be BLANK, or say 'BvMath/'
                            #                           0         1       2        3        4
							$sln_projpath{$projname} = [$projfile,$projff,$relpath,$dspfile,$fdspfil]; # relative project file, like '..\alut\path\alut.vcproj'
							prt( "Stored \$sln_projpath{$projname} = [0:$projfile,1:$projff,2:$relpath,3:$dspfile,4:$fdspfil]\n") if ($dbg_sl_15);
							$sln_projids{$projname}  = $projid;
							$sln_depends{$projname}  = '';	# start dependencies, if any
                            if ($dbg_sl_16) {
                                my $msg = $projname;
                                $msg .= ' ' while (length($msg) < 24);
                                $msg .= $projid;
                                prt("$msg\n");
                            }
						}
                        ### pgm_exit(1,"TEMP EXIT");
					} else {
						$msg = "WARNING: ";
						if (!length($projname)) {
							$msg .= "Failed to get a project name! ";
						} elsif ( !is_vcproj_ext($projfile) && !is_vcxproj_ext($projfile)) {
							$msg .= "Name [$projfile] NOT a VCPROJ nor VCXPROJ name! ";
						} else {
							$msg .= "Unable to locate file [$projff]! ";
						}
						$msg .= " Line is (trimmed)\n$tline";
                        prtw("$msg\n");
					}
				} else {
					prtw( "Warning: Part 2 of Project line did NOT split into 3 on comma!???\n" );
				}
			} else {
				prtw( "Warning: Project line did NOT split in 2 on equal sign!???\n" );
			}

			# to switch on $tryharder requires additional work on parsing this line
			# =====================================================================
			prtw("WARNING: line [$line] ...\n") if (!$gotproj);
			# =====================================================================
		} elsif ($inproj) {
			# in the Project section - look for END of section, and DEPENDENCIES
			# ProjectSection(ProjectDependencies)
			if ($tline eq 'EndProject') {
			###if ($line =~ /^EndProject\s*/)
				$inproj = 0;
			} else {
				if ($inpdeps) {
					if ($tline eq 'EndProjectSection' ) {
						$inpdeps = 0;
					} else {
						# collect dependencies
						@arr = split( '=', $line );
						$cnt = scalar @arr;
						if ($cnt == 2) {
							$arr[0] = trim_all($arr[0]);
							$arr[1] = trim_all($arr[1]);
							if ($arr[0] eq $arr[1]) {
								$projdeps = $sln_depends{$projname};	# extract dependencies, if any
								$projdeps .= '|' if (length($projdeps));
								$projdeps .= $arr[0];
								prt( "$pgmname: Proj $projname, dependant on $arr[0] ...\n" ) if ($dbg_sl_02);
								##prt( "Proj $projname, dependant on $projdeps ...\n" );
								$sln_depends{$projname} = $projdeps;
							} else {
								prtw( "Warning: Found different IDS '$arr[0]' NE '$arr[1]'!!! \n" );
							}
						} else {
							prtw( "Warning: Project DEPENDENCY line did NOT split in 2 on equal sign!???\n" );
							prtw( "line=$line" );
						}
					}
				} elsif ($line =~ /ProjectSection\s*\(\s*ProjectDependencies\s*\)/) {
					$inpdeps = 1;
				}
			}
		}
	}
	###prt( "Done $fil ... got ".scalar @proj_files." project files ...\n" );
    $cnt = 0;
    my $cmcnt = 0;
	foreach $projname (keys %sln_projects) {
        if (($projname eq 'ALL_BUILD')||($projname eq 'INSTALL')||($projname eq 'ZERO_CHECK')) {
            $cmcnt++;
        } else {
            $cnt++;
        }
    }
	prt( "Done $fil ... of ".scalar keys(%sln_projects)." items got $cnt project file(s)...\n" ) if (VERB9());
	# resolve dependencies, if possible - warn if NOT ...
	#resolve_depends();
    # Have STORED
    # $sln_projects{$projname} = $projff;
	# $sln_projpath{$projname} = $relpath; # can be BLANK, or say 'BvMath/'
    #                           0         1       2        3        4
	#$sln_projpath{$projname} = [$projfile,$projff,$relpath,$dspfile,$fdspfil]; # relative project file, like '..\alut\path\alut.vcproj'
	#prt( "Stored \$sln_projpath{$projname} = [0:$projfile,1:$projff,2:$relpath,3:$dspfile,4:$fdspfil]\n") if ($dbg_sl_15);
	# $sln_projids{$projname}  = $projid;
	# $sln_depends{$projname}  = '';	# start dependencies, if any
	foreach $projname (keys %sln_projects) {
		$projdeps = $sln_depends{$projname};
		if (length($projdeps)) {
			# there is LENGTH, convert giant CID to simple project names
			@arr = split( /\|/, $projdeps );	# split em up
			$cnt = scalar @arr;	# get count of split
			#prt( "Proj $projname, depends on $cnt = $projdeps ...\n" );
			$nmdeps = '';	# build simple NAME set
			foreach $depid (@arr) {
                # find project MATCHING that ID, in full list of IDs
                $fnd = 0;
                $list = '';
				foreach $pn (keys %sln_projids) {
					if ($pn ne $projname) {
						$projid = $sln_projids{$pn};
                        $list .= "|$projid";
						if ($depid eq $projid) {
							$nmdeps .= '|' if (length($nmdeps));
							$nmdeps .= $pn;
                            $fnd = 1;
							last;
						}
					}
				}
                if (!$fnd) {
                    prtw("Warning: Failed to FIND [$depid], in list \n[$list]\n!");
                }
			}
			@arr = split( /\|/, $nmdeps );
			prt( "$pgmname: proj $projname, depends on $nmdeps ...\n" ) if ($dbg_sl_03);
			if ($cnt != scalar @arr) {	# YEEK - Does NOT match - OH WELL
				prtw( "WARNING: proj [$projname] with depends [$projdeps] Failed to get SAME count $cnt - got ".scalar @arr." on split [$nmdeps]\n" );
                pgm_exit(1,"");
			}
			$sln_depends{$projname} = $nmdeps;
		}
	}
    # ====================================================================
    $hash{'SOLUTION'} = $fil;   # keep the SOLUTION files also
    $hash{'PROJECTS'} = { %sln_projects };
    $hash{'PROJPATH'} = { %sln_projpath };  # array refs [$projfile,$projff,$relpath]
    $hash{'DEPENDS'}  = { %sln_depends  };
    $hash{'PROJIDS'}  = { %sln_projids };
    $hash{'MISSED_FILES'} = { %missed_vcprojs }; # not found on DISK
    return \%hash;
}


sub process_in_file($) {
    my ($inf) = @_;
    my ($file);
    my ($proj,$d,$e) = fileparse($inf, qr/\.[^.]*/ );
    if (is_sln_ext($inf)) {
        my $rh = process_SLN_file2($inf);
        if (defined ${$rh}{'PROJECTS'}) {
            my $rph = ${$rh}{'PROJECTS'};
            my @arr = sort keys(%{$rph});
            my $cnt = 0;
            my $totcnt = scalar @arr;
            foreach $proj (@arr) {
                if ( !(($proj eq 'ALL_BUILD')||($proj eq 'INSTALL')||($proj eq 'ZERO_CHECK')) ) {
                    $cnt++;
                }
            }
            prt("Of $totcnt items found $cnt project(s) in $inf\n");
            foreach $proj (@arr) {
                $file = ${$rph}{$proj};
                prt("\nProject: $proj, File: $file\n") if (VERB9());
                process_xml_file($file,$proj);
                ### $load_log = 2;
            }
        } else {
            prt(Dumper($rh));
        }
    } elsif (is_vcxproj_ext($inf)) {
        prt("\nProject: $proj, File: $inf\n"); # if (VERB9());
        process_xml_file($inf,$proj);
    } else {
        prt("Can ONLY handle .sln or .vcxproj file, not [$e]! FIX ME!!!\n");
    }
}

sub process_in_file_simp($) {
    my ($inf) = @_;
    if (! open INF, "<$inf") {
        pgm_exit(1,"ERROR: Unable to open file [$inf]\n"); 
    }
    my @lines = <INF>;
    close INF;
    my $lncnt = scalar @lines;
    prt("Processing $lncnt lines, from [$inf]...\n");
    my ($line,$inc,$lnn);
    $lnn = 0;
    foreach $line (@lines) {
        chomp $line;
        $lnn++;
        if ($line =~ /\s*#\s*include\s+(.+)$/) {
            $inc = $1;
            prt("$lnn: $inc\n");
        }
    }
}

sub show_master_hash($) {
    my $rh = shift;
    # ====================================================================
    # A CONSOLE APP SHOWS: AppType: Application;Console
    # A WINDOWS APP SHOWS: AppType: Application;Windows
    # A SHARED  DLL SHOWS: AppType: DynamicLibrary;Windows
    # A STATIC  LIB SHOWS: AppType: StaticLibrary;
    #$master_hash{$proj}->{apptype} = $config;$subsystem;
    my @arr = sort mycmp_nc_sort keys(%{$rh});
    my $cnt = scalar @arr;
    my ($proj,$rph,$typ,$ra);
    prt("Found $cnt projects in [$in_file]\n") if (VERB9());
    # static libs first
    foreach $proj (@arr) {
        $rph = ${$rh}{$proj};
        $typ = ${$rph}{'apptype'};
        if ($typ =~ /StaticLibrary/) {
            prt("$proj: AppType: $typ\n");
            #$master_hash{$proj}->{sources} = [@a];
            $ra = ${$rph}{'sources'};
            if (VERB1()) {
                prt("$proj: Sources:\n".join("\n",@{$ra})."\n");
            } else {
                prt("$proj: Sources: ".join(" ",@{$ra})."\n");
            }
            # $master_hash{$proj}->{preprocs} = [@a];
            $ra = ${$rph}{'preprocs'};
            prt("$proj: PreProcs: ".join(";",@{$ra})."\n"); # if (VERB5());
            #$master_hash{$proj}->{addincs} = [@a];
            $ra = ${$rph}{'addincs'};
            $cnt = scalar @{$ra};
            if (VERB1() && ($cnt > 3)) {
                prt("$proj: AddIncs:\n".join("\n",@{$ra})."\n");
            } else {
                prt("$proj: AddIncs: ".join(";",@{$ra})."\n");
            }
            #$master_hash{$proj}->{libdeps} = [@a];
            $ra = ${$rph}{'libdeps'};
            #prt("$proj: LibDeps: ".join(" ",@{$ra})."\n");  # if (VERB5());
            #$master_hash{$proj}->{libdirs} = [@a];
            $ra = ${$rph}{'libdirs'};
            #prt("$proj: LibDirs: ".join(";",@{$ra})."\n");  # if (VERB5());
        }
    }
}


#########################################
### MAIN ###
parse_args(@ARGV);
process_in_file($in_file);
show_master_hash(\%master_hash);
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);
    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);
                    }
                }
                prt("Verbosity = $verbosity\n") if (VERB9());
            } elsif ($sarg =~ /^l/) {
                if ($sarg =~ /^ll/) {
                    $load_log = 2;
                } else {
                    $load_log = 1;
                }
                prt("Set to load log at end. ($load_log)\n") if (VERB9());
            } elsif ($sarg =~ /^o/) {
                need_arg(@av);
                shift @av;
                $sarg = $av[0];
                $out_file = $sarg;
                prt("Set out file to [$out_file].\n") if (VERB9());
            } elsif ($sarg =~ /^r/) {
                need_arg(@av);
                shift @av;
                $sarg = $av[0];
                $rel_dir = File::Spec->rel2abs($sarg);
                if (-d $rel_dir) {
                    prt("Set relative directory to [$rel_dir].\n") if (VERB9());
                } else {
                    pgm_exit(1,"ERROR: Rel directory does NOT exist [$rel_dir]! [$sarg]\n");
                }
            } else {
                pgm_exit(1,"ERROR: Invalid argument [$arg]! Try -?\n");
            }
        } else {
            $in_file = $arg;
            prt("Set input to [$in_file]\n") if (VERB9());
        }
        shift @av;
    }

    if ($debug_on) {
        prtw("WARNING: DEBUG is ON!\n");
        if ((length($in_file) ==  0) && $debug_on) {
            $in_file = $def_file;
            prt("Set DEFAULT input to [$in_file]\n");
            $load_log = 2;
        }
    }
    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(" --rel <dir>   (-r) = Output sources relative to this directory.\n");
    prt(" --out <file>  (-o) = Write output to this file.\n");
}

# eof - template.pl
