#!/perl -w
# NAME: find_in_inctrail.pl
# AIM: Given an in C/C++ file, check for #include "file" and #include <file>
# statements, and follow the trail, listing ALL included files, included ...
# And SEARCH each for a $find text, and advise ...
# 02/08/2008 geoff mclane http://geoffair.net/mperl
###################################################################
use strict;
use warnings;
use File::Basename;
require 'logfile.pl' or die "Unable to load logfile.pl ...\n";
# log file stuff
my ($LF);
my $pgmname = $0;
if ($pgmname =~ /\w{1}:\\.*/) {
	my @tmpsp = split(/\\/,$pgmname);
	$pgmname = $tmpsp[-1];
}
my $outfile = "temp.$pgmname.txt";
open_log($outfile);

# debug
my $dbg1 = 0;	# show all config lines
my $dbg2 = 0;	# show 'Processing ...'
my $dbg3 = 0;	# show expansionss ...
my $dbg4 = 0;	# show vc8 BAT loading ...
my $dbg5 = 0;	# show folder about to be searched
my $dbg6 = 0;	# show INVALID INCLUDE folders ...
my $dbg7 = 0;	# show ALL paths TRIED ...
my $verb3 = 0;	# show sorting
my $dbg8 = 0;   # show "\nGot $lc lines of [$inf] to process ...
my $dbg9 = 0;   # show "$addcnt:$ic $line - $ifil - [$ff] - $msg
my $dbg10 = 0;  # show "Found $ic in [$inf] ...

my @warnings = ();
my $fin_file = 'C:\Projects\Tidy\tidy4p5\include\tidy.h';
my $find = 'EXPORT';
###my $find = 'TIDY_STRUCT';

my @findlist = ();
my $fndcnt = 0;

my @included = ();
my $inccount = 0;
my %byfolder = ();
my @foundlst = ();
my $cicnt = 0;
my $i = 0;
my $addcnt = 0;
my $oldcnt = 0;
my $newcnt = 0;
my $diffcnt = 0;
my @rel_folders = ( '..\..\..', '..\..\..\include' );
my ($fin_name, $fin_folder) = fileparse($fin_file);
my @include_folders = get_INCLUDE_Folders($fin_folder);
my $incfcnt = scalar @include_folders;
prt( "Got $incfcnt INCLUDE folders ...\n" );
process_file($fin_file, 0);
$cicnt = scalar @included;
prt( "\nGot TOTAL $cicnt includes from [$fin_file] ...\n" );
for ($i = 0; $i < $cicnt; $i++) {
	my $f = $included[$i][0];
	my $ord = $included[$i][1];
    if (-f $f) {
    	prt( "$ord $f - ok\n" );
    } else {
        prt( "$ord $f - NOT FOUND\n" );
    }
	my ($nam, $dir) = fileparse($f);
	if (defined $byfolder{$dir}) {
		$byfolder{$dir} .= '*'.$nam;
	} else {
		$byfolder{$dir} .= $nam;
	}
}
$fndcnt = scalar @findlist;
if ($fndcnt) {
    prt( "Found $find $fndcnt times, start with $fin_file ...\n" );
    # push(@findlist, [$lnnum, $line, $inf]);
    for ($i = 0; $i < $fndcnt; $i++) {
        prt( $findlist[$i][2].":".$findlist[$i][0].": [".$findlist[$i][1]."]\n" );
    }
} else {
    prt( "NO FINDS of $find, start with $fin_file ...\n" );
}

#prt( "\nBY FOLDER - TOTAL $cicnt includes from [$fin_file] ...\n" );
#foreach my $dir (sort (keys(%byfolder))) {
#	my $fnms = $byfolder{$dir};
#	my @nms = split(/\*/,$fnms);
#	my @nmss = sort @nms;
#	prt( "$dir - ".scalar @nms." headers ...\n" );
#	prt( join(", ", @nmss)."\n" );
#}
# show_found_list();

close_log($outfile,1);
exit(0);

sub add_2_included {
	my ($fil, $in) = @_;
	my $lcfil = lc($fil);
	my $cicnt = scalar @included;
	for (my $j = 0; $j < $cicnt; $j++) {
		my $got = $included[$j][0];	# extract full file name
		my $lcgot = lc($got);		# to lower case
		if ($lcfil eq $lcgot) {		# if equal
			my $cin = $included[$j][2];	# get (list) of in
			my @carr = split(/\*/,$cin);	# split list
			my $fnd = 0;	# not found yet
			foreach my $tin (@carr) {	# process each in
				if ($tin eq $in) {
					$fnd = 1;	# found it
					last;
				}
			}
			if (!$fnd) {
				$cin .= '*'.$in;	# append a new 'in'
				$included[$j][2] = $cin;	# store this included in ...
			}
			return 0;				# do NOT add
		}
	}
	$inccount++;
	push(@included, [$fil, $inccount, $in]);
	return 1;
}

sub is_same_file {
	my ($f1, $f2) = @_;
	my $len = length($f1);
	if ($len != length($f2)) {
		return 0;	# not the SAME
	}
	$f1 =~ s/\//\\/g;
	$f2 =~ s/\//\\/g;
	my $lcf1 = lc($f1);
	my $lcf2 = lc($f2);
	my $i = 0;
	while ($i < $len) {
		if (substr($lcf1,$i,1) ne substr($lcf2,$i,1)) {
			return 0;
		}
		$i++;
	}
	return 1;
}

sub add_2_found_list {
	my ($inf, $ic, $fls) = @_;
	my ($nm, $dir) = fileparse($inf);
	if ($nm =~ /^pshpack/i) {
		return 0;
	}
	if ($nm =~ /^poppack/i) {
		return 0;
	}
	my $cnt = scalar @foundlst;
	for (my $f = 0; $f < $cnt; $f++) {
		my $ff = $foundlst[$f][1];
		if (is_same_file($inf, $ff)) {
			return 0;
		}
	}
	push(@foundlst, [$ic, $inf, $fls]);
	return 1;
}

# put least first
sub mycmp_ascend {
   if (${$a}[0] < ${$b}[0]) {
      prt( "-[".${$a}[0]."] < [".${$b}[0]."]\n" ) if $verb3;
      return -1;
   }
   if (${$a}[0] > ${$b}[0]) {
      prt( "+[".${$a}[0]."] < [".${$b}[0]."]\n" ) if $verb3;
      return 1;
   }
   prt( "=[".${$a}[0]."] < [".${$b}[0]."]\n" ) if $verb3;
   return 0;
}


sub show_found_list {
	my @sfoundlst = sort mycmp_ascend @foundlst;
	my $cnt = scalar @sfoundlst;
	my $fc = 0;
	my ($f, $ff, $ic, $nm, $dir, $len, $min, $msg, $fs);
	$min = 0;
    prt( "\nOutput list of $cnt headers found starting with $fin_file ...\n" );
	for ($f = 0; $f < $cnt; $f++) {
		$ff = $sfoundlst[$f][1];
		($nm,$dir) = fileparse($ff);
		$len = length($nm);
		$min = $len if ($len > $min);
	}
	$min += 6;
	for ($f = 0; $f < $cnt; $f++) {
		$fs = $sfoundlst[$f][2];
		$ff = $sfoundlst[$f][1];
		$ic = $sfoundlst[$f][0];
		$fc++;
		($nm,$dir) = fileparse($ff);
		$msg = "$fc";
		$msg = ' '.$msg while (length($msg) < 3);
		$msg .= ": $nm";
		$msg .= ' ' while (length($msg) < $min);
		$msg .= "$ic ";
		$fs =~ s/\*/, /g;
		$msg .= $fs;
		prt( "$msg\n" );
	}
    prt( "Done list of $cnt headers found starting with $fin_file ...\n" );
}

sub C_comment_starts {
    my ($txt) = shift;
    my $len = length($txt);
    my $ptxt = '';
    my $ttxt = '';
    my ($k, $ch, $pch, $k2, $nch);
    for ($k = 0; $k < $len; $k++) {
        $k2 = $k + 1;
        $ch = substr($txt,$k,1);
        $nch = (($k2 < $len) ? substr($txt,$k2,1) : '');
        if (($ch eq '/')&&($nch eq '*')) {
            $ttxt = substr($txt,($k2+1));
            return $k2, $ptxt, $ttxt;   # return offset, previous and begin comment
        }
        $pch = $ch;
        $ptxt .= $ch;
    }
    return 0, $ptxt, $ttxt;
}

sub inline_comment_starts {
    my ($txt) = shift;
    my $len = length($txt);
    my $ptxt = '';
    my ($k, $ch, $pch, $k2, $nch);
    for ($k = 0; $k < $len; $k++) {
        $k2 = $k + 1;
        $ch = substr($txt,$k,1);
        $nch = (($k2 < $len) ? substr($txt,$k2,1) : '');
        if (($ch eq '/')&&($nch eq '/')) {
            return $k2, $ptxt;   # return offset, previous
        }
        $pch = $ch;
        $ptxt .= $ch;
    }
    return 0, $ptxt;
}

sub C_comment_ends {
    my ($txt) = shift;
    my $len = length($txt);
    my $ttxt = '';
    my ($k, $ch, $pch, $k2, $nch);
    for ($k = 0; $k < $len; $k++) {
        $k2 = $k + 1;
        $ch = substr($txt,$k,1);
        $nch = (($k2 < $len) ? substr($txt,$k2,1) : '');
        if (($ch eq '*')&&($nch eq '/')) {
            $ttxt = substr($txt,($k2+1));
            return $k2, $ttxt;  # return trailing 
        }
        $pch = $ch;
    }
    return 0, $ttxt;
}


sub process_file {
	my ($inf, $lev) = @_;
	my $ic = 0;
	my $fils = '';
    my $lnnum = 0;
    my ($isc,$ptxt,$ttxt,$ise,$atxt,$ctxt);
    my $incomm = 0;
	if (open INF, "<$inf") {
		my @lines = <INF>;
		close INF;
		my ($nm, $dir) = fileparse( $inf );
		my $lc = scalar @lines;
		prt( "\nGot $lc lines of [$inf] to process ...\n" ) if ($dbg8);
		my $msg = '';
		my $rpt = 0;
		foreach my $line (@lines) {
            $lnnum++;
			chomp $line;
			$line = trim_all($line);
            if ($incomm) {
                ($ise,$atxt) = C_comment_ends($line);
                if ($ise) {
                    $incomm = 0;
                    $ctxt = trim_all($atxt);
                    if (length($ctxt)) {
                        $line = $ctxt;
                    } else {
                        next;
                    }
                } else {
                    next;
                }
            }
            ($isc,$ptxt,$ttxt) = C_comment_starts($line);
            if ($isc) {
                # C comment starting ...
                ($ise,$atxt) = C_comment_ends($ttxt);
                if ($ise) {
                    $ptxt = trim_all($ptxt);
                    $atxt = trim_all($atxt);
                    $ctxt = $ptxt;
                    $ctxt .= ' ' if (length($ctxt) && length($atxt) && ($atxt ne ';'));
                    $ctxt .= $atxt if length($atxt);
                    $ctxt = trim_all($ctxt);
                    if (length($ctxt)) {
                        $line = $ctxt;
                    } else {
                        next;
                    }
                } else {
                    $incomm = 1;
                    $ptxt = trim_all($ptxt);
                    if (length($ptxt)) {
                        $line = $ptxt;
                    } else {
                        next;
                    }
                }
            } else {
                ($isc,$ptxt) = inline_comment_starts($line);
                if ($isc) {
                    $ctxt = trim_all($ptxt);
                    if (length($ctxt)) {
                        $line = $ctxt;
                    } else {
                        next;
                    }
                }
            }

            if ($line =~ /$find/) {
                push(@findlist, [$lnnum, $line, $inf]);
            }
			if ($line =~ /^#\s*include\s+(.+)\s*/) {
				$ic++;
				my $lbal = $1;
				my $ifil = '';
				if ($lbal =~ /<(.+)>/) {
					$ifil = $1;
				} elsif ($lbal =~ /"(.*)"/) {
					$ifil = $1;
				}
				if (length($ifil) == 0) {
					prt( "CHECK ME:$lnnum: line[$line] tail[$lbal] ... from $inf ...\n" );
					next;
				}
				my $fnd = 0;
				#$ifil =~ s/<//;
				#$ifil =~ s/>//;
				#$ifil =~ s/"//g;
				my $ff = $dir;
				$ff .= "\\" if !(substr($dir,-1) =~ /(\\|\/)/);
				$ff .= $ifil;
				$fils .= '*' if (length($fils));
				$fils .= $ifil;
				$msg = "FAILED";
				$rpt = 0;
				prt( "Trying [$ff] LOCAL\n" ) if ($dbg7);
				if (-f $ff) {
					$msg = "OK";
					my $add = add_2_included( $ff, $inf );
					if ($add) {
						$msg .= " ADDED";
						$addcnt++;
						process_file( $ff, ($lev + 1) );
					} else {
						$msg .= " REPEAT";
						$rpt = 1;
					}
					$fnd = 1;
				} else {
					# NOT found in LOCAL folder
					foreach my $rfld (@rel_folders) {
						my $ff1 = $dir;
						$ff1 .= "\\" if !(substr($ff1,-1) =~ /(\\|\/)/);
						$ff1 .= $rfld;
						$ff1 .= "\\" if !(substr($ff1,-1) =~ /(\\|\/)/);
						$ff1 .= $ifil;
						$ff1 = fix_rel($ff1);
						prt( "Trying [$ff1] RELATIVE\n" ) if ($dbg7);
						if (-f $ff1) {
							$ff = $ff1;
							$msg = "OK";
							my $add = add_2_included( $ff, $inf );
							if ($add) {
								$msg .= " ADDED";
								$addcnt++;
								process_file( $ff, ($lev + 1) );
							} else {
								$msg .= " REPEAT";
								$rpt = 1;
							}
							$fnd = 1;
							last;
						}
					}
					if (!$fnd) {
						foreach my $ifld (@include_folders) {
							my $ff2 = $ifld;
							$ff2 .= "\\" if !(substr($ff2,-1) =~ /(\\|\/)/);
							$ff2 .= $ifil;
							prt( "Trying [$ff2] SYSTEM\n" ) if ($dbg7);
							if (-f $ff2) {
								$ff = $ff2;
								$msg = "OK";
								my $add = add_2_included( $ff, $inf );
								if ($add) {
									$msg .= " ADDED";
									$addcnt++;
									process_file( $ff, ($lev + 1) );
								} else {
									$msg .= " REPEAT";
									$rpt = 1;
								}
								$fnd = 1;
								last;
							}
						}
					}
				}
				prt( "$addcnt:$ic $line - $ifil - [$ff] - $msg\n" ) if (!$rpt && $dbg9);
			}
		}
		prt( "Found $ic in [$inf] ...\n" ) if ($dbg10);
		add_2_found_list( $inf, $ic, $fils );
	} else {
		prt( "ERROR: Failed to open file [$inf] ...\n" );
	}
}

#####################################################################
######### getting the INCLUDE folders, either from the ENVIRONMENT
######### or from where MSVC8 stroes its stuff

sub load_vc8_cfg {
	my ($vc8c) = shift;
	my @v8_incs = ();
	if (open INF, "<$vc8c") {
		my @clns = <INF>;
		close INF;
		foreach my $cln (@clns) {
			chomp $cln;
			$cln = trim_all($cln);
			prt( "$cln\n" ) if ($dbg1);
			if ($cln =~ /include=\"(.+)\"/i) {
				my $iln = $1;
				my @vc8i = split(';',$iln);
				prt( "INCLUDE=[$iln]\n" );
				foreach my $itm (@vc8i) {
					push(@v8_incs, $itm);
				}
			}
		}
	} else {
		prt( "WARNING: can not open [$vc8c] ... $! ...\n" );
	}
	return @v8_incs;
}


sub load_vc8_bat {
	my ($vc8b) = shift;
	my @v8_folders = ();
	my @v8_incs = ();
	my %v8_hash = ();
	if (open INB, "<$vc8b") {
		my @lns = <INB>;
		close INB;
		foreach my $ln (@lns) {
			chomp $ln;
			$ln = trim_all($ln);
			if ($ln =~ /\@*SET\s+(.*)/) {
				my @arr = split(/=/,$1);
				my $sz = scalar @arr;
				if ($sz == 2) {
					my $ky = uc($arr[0]);
					my $val = $arr[1];
					$v8_hash{$ky} = $val;
					prt( "[$ky]=[$val]\n" ) if ($dbg4);
					if ($ky =~ /^VCINSTALLDIR$/i) {
						# got the INSTALL DIECTORY
						my $vc8_cfg = $val. "\\vcpackages\\vcprojectengine.dll.config";
						if (-f $vc8_cfg) {
							@v8_incs = load_vc8_cfg($vc8_cfg);
						} else {
							prt( "WARNING: [$vc8_cfg] does not exist ...\n" );
						}
					}

				} else {
					prt( "SET $1\n" );
				}
			}
		}
		foreach my $item (@v8_incs) {
			# expand
			if ($item =~ /.*\$\((.+)\).+/) {
				my $eit = uc($1);
				prt( "Item [$eit] in [$item] needs expansion ...\n" ) if ($dbg3);
				foreach my $key (keys %v8_hash) {
					if ($key eq $eit) {
						$item =~ s/\$\($key\)/$v8_hash{$key}\\/i;
						prt( "New item = [$item] ...\n" ) if ($dbg3);
						last;
					}
				}
			}
			push(@v8_folders, $item) if (length($item));
		}
	} else {
		prt( "WARNING: No open of [$vc8b] ... $! ...\n" );
	}
	return @v8_folders;
}


sub get_INCLUDE_Folders {
	my ($inf) = shift;	# this is the LOCAL folder
	my @fldrs1 = ();
	my @fldrs2 = ();
	my @fldrs3 = ();
	my @fldrsok = ();
	my $okcnt = 0;
	my $failed = 0;
	my $valcnt = 0;
	my $envstg = $ENV{"INCLUDE"};	# check INLCUDE in environment
	my $vc8_env = $ENV{"VS80COMNTOOLS"};
	my $psdk = $ENV{"PSDK_DIR"};
	my $dxsdk = $ENV{"DXSDK_DIR"};	# =C:\Program Files\Microsoft DirectX SDK (October 2006)\
	my $fdr = '';
	if (defined $envstg) {
		@fldrs1 = split(';',$envstg);
	} else {
		prt( "INLCUDE NOT found in environment ...\n" );
	}
	if (defined $vc8_env) {
		# we have MSVC8
		my $vc8_bat = $vc8_env . "vsvars32.bat";
		if (-f $vc8_bat) {
			push(@fldrs2, load_vc8_bat($vc8_bat));
		} else {
			prt( "WARNING: [$vc8_bat] not found ...\n" );
		}
	}
	if (defined $psdk) {
		push(@fldrs3,$psdk);
	} else {
		prt( "PSDK_DIR NOT found in environment ...\n" );
	}
	if (defined $dxsdk) {
		push(@fldrs3,$dxsdk);
	} else {
		prt( "DXSDK_DIR NOT found in environment ...\n" );
	}
	foreach $fdr (@fldrs1) {
		if (-d $fdr) {
			push(@fldrsok, $fdr) if (!same_folder($fdr,$inf));
			prt( "VALID [$fdr] ...\n" );
			$valcnt++;
		} else {
			prt( "Discarding [$fdr] as INVALID ...\n" ) if ($dbg6);
			$failed++;
		}
	}
	foreach $fdr (@fldrs2) {
		if (-d $fdr) {
			push(@fldrsok, $fdr) if (!same_folder($fdr,$inf));
			prt( "VALID [$fdr] ...\n" );
			$valcnt++;
		} else {
			prt( "Discarding [$fdr] as INVALID ...\n" ) if ($dbg6);
			$failed++;
		}
	}
	foreach $fdr (@fldrs3) {
		if (-d $fdr) {
			push(@fldrsok, $fdr) if (!same_folder($fdr,$inf));
			prt( "VALID [$fdr] ...\n" );
			$valcnt++;
		} else {
			prt( "Discarding [$fdr] as INVALID ...\n" ) if ($dbg6);
			$failed++;
		}
	}
	$okcnt = scalar @fldrsok;
	prt( "Found $okcnt ($valcnt) folders, and $failed failed ...\n" );
	return @fldrsok;
}

#####################################################################

sub unix_2_dos {
	my ($f) = shift;
	$f =~ s/\//\\/g;
	return $f;
}

# fix relative path
sub fix_rel { # fixed 26/12/2007 to remove '\\' entries
	my ($path) = shift;
	$path = unix_2_dos($path);	# ensure DOS separator
	my @a = split(/\\/, $path);	# split on DOS separator
	my $npath = '';
	my $wmsg = '';
	my $max = scalar @a;
	my @na = ();
	for (my $i = 0; $i < $max; $i++) {
		my $p = $a[$i];
		if ($p eq '.') {
			# ignore this
		} elsif ($p eq '..') {
			if (@na) {
				pop @na;	# discard previous
			} else {
				$wmsg = "WARNING: Got relative .. without previous!!! [$path]";
				prt( "$wmsg\n" );
				push(@warnings,$wmsg);
			}
		} elsif (length($p)) {	# added 26/12/2007
			push(@na,$p);
		}
	}
	foreach my $pt (@na) {
		$npath .= "\\" if length($npath);
		$npath .= $pt;
	}
	return $npath;
}


sub same_folder {
	my ($fd1, $fd2) = @_;
	$fd1 = unix_2_dos($fd1);
	$fd2 = unix_2_dos($fd2);
	$fd1 =~ s/\\$//;
	$fd2 =~ s/\\$//;
	my $lfd = length($fd1);
	if ($lfd != length($fd2)) {
		return 0;	# NOT same length
	}
	for (my $k = 0; $k < $lfd; $k++) {
		if (lc(substr($fd1,$k,1)) ne lc(substr($fd2,$k,1))) {
			return 0;	# different
		}
	}
	return 1;	# ARE THE DOS SAME
}

# eof - find_in_inctrail.pl
