#!/usr/local/bin/perl -w
use strict;
$|=1;

# assumes $ARGV[0] = src, $ARGV[1] = dest
# creates global_all_dll.c/h in dest directory

my $fname_out="global_all_dll";
my $homedir=`pwd`; chomp $homedir;
my $srcdir=$homedir.'/'.$ARGV[0];
my $destdir=$homedir.'/'.$ARGV[1]; shift @ARGV; shift @ARGV;

chdir($srcdir);
open(I,"find . -name \"*.c\" -print |");
my @files=<I>;
close(I);
open(I,"find . -name \"*.h\" -print |");
my @filesH=<I>;
close(I);
chdir($homedir);

my @globV;
my @includes; my @defines; my %source_;
my %incD; my %defD;
$incD{"__FILE__"}=1; # ignore self-includes
$incD{"<windows.h>"}=1; # ignore windows.h
foreach my $fname_in (@files) {
  my @source; my $line=""; my $level=0;
  my $comment=0;
  chomp $fname_in;
  my $loc_path=substr($fname_in,0,rindex($fname_in,"/"));
  system('mkdir','-p',$destdir.'/'.$loc_path);

  open(I,$srcdir.'/'.$fname_in);
  while (<I>) {
    chomp unless (/\\$/);
    while (/\\$/) {
      $_.=<I>;
    }
    if (/^#[ \t]*include/) {
      s/[ \t][ \t]*/ /g;
      s/^# include/#include/;
      s/ *\/\*.*?\*\/ *//g;
      my $ix=substr($_,9);
      if (substr($ix,0,1) eq '"') {
        $ix='"'.$loc_path.'/'.substr($ix,1);
      }
      push @includes,$ix unless defined($incD{$ix});  # collect all include files...
      $incD{$ix}++;
      push @source, ['#INC',$_];
    } elsif (/^#define/) {
      my $nL = () = m/\n/g;
      push @source,[($nL>1 ? '#DEF_PROG' : '#DEF'),$_];
      if ($nL<=1) { push @defines,$_ unless defined($defD{$_}); $defD{$_}++; }
    } elsif (/^#if 0[ \t]*$/) {
      push @source,['#OTH',$_];
      do { $_=<I>; chomp $_; push @source,['#OTH',$_] } until (/^#endif/);
    } elsif (/^#/) {
      if ($level==0) { push @source,['#OTH',$_]; } else { $line.=$_."\n"; }
    } elsif (/^[ \t]*$/) {
      if ($level==0) { push @source,['EMPTY_LINE',$_]; } else { $line.=$_."\n"; }
    } elsif (/^[ \t]*typedef /) {
      my ($o_p, $c_p);
      $o_p = () = m/\{/g;
      $c_p = () = m/\}/g;
      $_.=" ";
      while (!(/\;[ \t]*$/ && $o_p==$c_p)) {
        $_.=<I>;
        $o_p = () = m/\{/g;
        $c_p = () = m/\}/g;
      };
      $_ =~ s/ $//;
      push @source,['PROG',$_];
    } else {
      $line.=$_."\n";
      if (/\/\*/ && !/\*\//) { $comment=1; }
      if (/^[ \t]*\/\*/ && /\*\/[ \t]*$/) { $comment=1; }
      my $o_p = () = m/\{/g;
      my $c_p = () = m/\}/g;
      $level+=($o_p-$c_p);
      if (($level==0) && (($comment && /\*\//) || (!$comment && (/;/ || /}/)))) {
        chomp $line;
        push @source,[($comment ? 'COMMENT' : ($line =~ /\(/ ? 'PROG':'VAR')),$line];
        $line="";
      }
      if (/\*\//) { $comment=0; }
    }
  }
  chomp $line;
  push @source,['PROG',$line];
  close(I);

  my $nGlobV=0;
  for (my $i=0; $i<=$#source; $i++) {
    my $s=$source[$i];
#    print $$s[0],"\n",$$s[1],"\n\n";
    if ($$s[0] eq 'VAR') {
      $$s[1] =~ s/ *\/\*.*?\*\/ *//g;
      (my $pre, my $init)=split(/[ \t][ \t]*=[ \t][ \t]*/,$$s[1],2);
      if ($pre !~ /\bconst\b/ && $pre !~ /\bstruct\b/) {  # is a global nonconst var
        $pre =~ s/\bstatic\b//; $pre =~ s/^[ \t]//g; $pre =~ s/[ \t]$//g;
        my @parts = split(/[ \t][ \t]*/,$pre);
        my $name= $parts[$#parts]; $name =~ s/;$//; $name =~ s/^\*//g;
        ($name, my $rest)=split(/\[/,$name,2);
        if (defined($init)) { $init =~ s/\n$//g; $pre.=";";};
        my $comment=undef;
        if ($i>0 && $source[$i-1]->[0] eq 'COMMENT') {
          $comment=$source[$i-1]->[1];
          $source[$i-1]->[0]='REMOVE';
        }
        push @globV, [$name,$pre,$init,$comment];
        print "Found $name ($pre)\n";
        $nGlobV++;
        $$s[0]='REMOVE';
      }
    }
  }
  $source_{$fname_in}=\@source;
  print "Found $nGlobV global nonconst variables in $fname_in.\n";
}

my %addFunc;
goto filesH;

files:
foreach my $fname_in (@files) {
  # Now, output the modified C files
  chomp $fname_in;
  my $loc_path=substr($fname_in,0,rindex($fname_in,"/"));
  my @source=(); my $isFirstInc=1;
  foreach my $s (@{$source_{$fname_in}}) {
    push @source, $s;
    if ($isFirstInc && $$s[0] eq '#INC') {
      if (defined($addFunc{$fname_in})) {
        push @source, ['PROG', $addFunc{$fname_in}];
      }
      $isFirstInc=0;
    }
  }

  open(O,'> '.$destdir.'/'.$fname_in);
  my $include_added=0;
  foreach my $s (@source) {
    if ($$s[0] eq '#INC' && !$include_added) {
      print O "#include <e32std.h>\n";
      my $loc_num = () = ($loc_path =~ m/\//g);
      print O "#include \"".("../" x $loc_num).$fname_out.".h\"\n";
      print O $$s[1],"\n" if ($$s[1] =~ /\_\_FILE\_\_/);
      $include_added=1;
    } elsif ($$s[0] eq '#INC' && $include_added) {
      # do nothing - all includes are in global_all_dll.h
      # otherwise, a lot of warning on duplicate definitions appear
      print O $$s[1],"\n" if ($$s[1] =~ /\_\_FILE\_\_/);
    } elsif ($$s[0] ne 'REMOVE') {
      if ($$s[0] eq 'PROG' || $$s[0] eq '#DEF_PROG') {
        # replace nonconst variables by calling Dll::Tls()
        my $line=$$s[1];
        foreach my $v (@globV) {
          my $repl = '(((struct global_all_dll *)Dll::Tls())->'.$$v[0].')';
          my $newline="";
          for (my $i=0; $i<length($line); $i++) {
            if (substr($line,$i,length($$v[0])) eq $$v[0] && substr($line,$i-1,1) =~ /^[ \t\+\-=\/\*\&,\(\):\?]$/ && substr($line."\n",$i+length($$v[0]),1) =~ /^[\[ \t+\-=\/\*\,\)\(\;\n]$/) {
              $newline.=$repl; $i+=length($$v[0])-1;
            } else { $newline.=substr($line,$i,1); }
          }
          $line=$newline;
        }
        print O $line,"\n";
      } else {
        print O $$s[1],"\n";
      }
    }
  }
  close(O);
  print "$fname_in\n";
}
goto ende;

filesH:

my %fC;
foreach my $fname_in (@files) {
  $fC{$fname_in}=1;
}

foreach my $fname_in (@filesH) {
  chomp $fname_in;
  open(I,$srcdir.'/'.$fname_in);
  open(O,'> '.$destdir.'/'.$fname_in);
  my $loc_path=substr($fname_in,0,rindex($fname_in,"/"));

  my $flag=0;
  while (<I>) {
    chomp;
    print O $_,"\n";
    if (/^#ifndef /) {$flag=1;}
    if (/^#define / && $flag==1) {
#      my $loc_num = () = ($loc_path =~ m/\//g);
#      print O "#include <e32std.h>\n";
#      print O "#include \"".("../" x $loc_num).$fname_out.".h\"\n";
      last; 
    }
  }

  my $fLevel=0;
  while (<I>) {
    chomp;
    if (/^[ \t]*typedef /) {
      my ($o_p, $c_p);
      $o_p = () = m/\{/g;
      $c_p = () = m/\}/g;
      $_.=" ";
      while (!(/\;[ \t]*$/ && $o_p==$c_p)) {
        $_.="\n".<I>;
        $o_p = () = m/\{/g;
        $c_p = () = m/\}/g;
      };
      $_ =~ s/ $//;
      print O $_,"\n";
      next;
    }
    if ($fLevel>0 || (/^[ \t]*static.*__inline/)) {
      my $x=$fname_in; $x =~ s/\.h$/.c/;
      if ($fLevel==0) {
        if (defined($fC{$x})) { $_=~ s/^[ \t]*static.*__inline//; }
        while ($_ !~ /\{/) { $_.=" ".<I> };
        if (defined($fC{$x})) { print O "extern ",substr($_,0,index($_,"{")-1),";\n"; }
      }
      my $o_p = () = m/\{/g;
      my $c_p = () = m/\}/g;
      $fLevel+=($o_p-$c_p);
      if (defined($fC{$x})) { $addFunc{$x}.=$_."\n"; } else { print O $_,"\n"; };
      next;
    }
    if ($fLevel>0) { next; }

    my $line=$_;
    my $isR=0;
    foreach my $v (@globV) {
      my $repl = '(((struct global_all_dll *)Dll::Tls())->'.$$v[0].')';
      my $newline="";
      for (my $i=0; $i<length($line); $i++) {
        if (substr($line,$i,length($$v[0])) eq $$v[0] && substr($line,$i-1,1) =~ /^[ \t\+\-=\/\*\&,\(\):\?]$/ && substr($line."\n",$i+length($$v[0]),1) =~ /^[\[ \t+\-=\/\*\,\)\(\;\n]$/) {
          $newline.=$repl; $i+=length($$v[0])-1;
          $isR++;
        } else { $newline.=substr($line,$i,1); }
      }
      $line=$newline;
    }
    if ($isR>0 && $line =~ /^[ \t]*extern /) { $line=""; }
    print O $line,"\n";
  }

  print "$fname_in\n";
  close(I); close(O);
}
goto files;

ende:

open(O,'>'.$destdir.'/'.$fname_out.'.h');
print O "#ifndef _GLOBAL_ALL_DLL_H\n";
print O "#define _GLOBAL_ALL_DLL_H\n\n";

foreach my $i (@includes) {
  print O "#include ",$i,"\n";
}
print O "\n";
print O "struct global_all_dll {\n";
foreach my $v (@globV) {
  if (defined($$v[3])) { print O $$v[3],"\n"; };
  print O "  ",$$v[1],"\n";
}
print O "};\n\n";
print O "#endif\n";
close(O);

open(O,'>'.$destdir.'/'.$fname_out.'.c');
print O "#include \"".$fname_out.".h\"\n";
print O "#include \<e32std.h\>\n\n";
# removed! does not actually work :->>
#foreach my $d (@defines) {
#  print O $d,"\n";
#}
print O "\n\n";
print O "int init_thread_local_storage(void) {\n";
print O '/* General initialization routine - call BEFORE any call to xvid */',"\n";
print O "  struct global_all_dll *p=malloc(sizeof(struct global_all_dll));\n";
print O "  Dll::SetTls(p);\n\n";
foreach my $v (@globV) {
  if (defined($$v[3])) { print O $$v[3],"\n" unless (!defined($$v[2])) };
  if (defined($$v[2])) {
    if ($$v[2] =~ /\{/) {
      my $set_cmd=$$v[1]; $set_cmd=~ s/;[ \t]*$//; $set_cmd.=" = ".$$v[2];
      print O $set_cmd,"\n";
      print O "p->",$$v[0]," = ",$$v[0],";\n\n";
    } else {
      print O "p->",$$v[0]," = ",$$v[2],"\n\n";
    }
  }
}
print O "\n  return KErrNone;\n}\n";
close(O);

