#!/usr/bin/env tclsh
###############################################################################
# BRLTTY - A background process providing access to the console screen (when in
#          text mode) for a blind person using a refreshable braille display.
#
# Copyright (C) 1995-2009 by The BRLTTY Developers.
#
# BRLTTY comes with ABSOLUTELY NO WARRANTY.
#
# This is free software, placed under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any
# later version. Please see the file LICENSE-GPL for details.
#
# Web Page: http://mielke.cc/brltty/
#
# This software is maintained by Dave Mielke <dave@mielke.cc>.
###############################################################################

proc getProgramName {} {
   global argv0
   return [file tail $argv0]
}

proc writeProgramMessage {message} {
   puts stderr "[getProgramName]: $message"
   flush stderr
}

if {[catch [list package require Tclx] response] != 0} {
   proc lvarcat {list elements} {
      uplevel 1 [list lappend $list] $elements
   }

   proc intersect3 {list1 list2} {
      foreach number {1 2} {
         set length$number [llength [set list$number [lsort [set list$number]]]]
         set index$number 0
         set only$number [list]
      }
      set both [list]

      while {1} {
         if {$index1 == $length1} {
            if {$index2 < $length2} {
               lvarcat only2 [lrange $list2 $index2 end]
            }
            break
         }

         if {$index2 == $length2} {
            lvarcat only1 [lrange $list1 $index1 end]
            break
         }

         switch -exact -- [string compare [lindex $list1 $index1] [lindex $list2 $index2]] {
           -1 {
               lappend only1 [lindex $list1 $index1]
               incr index1
            }

            1 {
               lappend only2 [lindex $list2 $index2]
               incr index2
            }

            0 {
               lappend both [lindex $list1 $index1]
               incr index1
               incr index2
            }
         }
      }

      return [list $only1 $both $only2]
   }

   proc lrmdups {list} {
      return [lsort -unique $list]
   }

   proc lempty {list} {
      return [expr {[llength $list] == 0}]
   }

   if {$tcl_version < 8.4} {
      proc readdir {directory} {
         set workingDirectory [pwd]
         cd $directory
         set names [glob -nocomplain *]
         cd $workingDirectory
         return $names
      }
   } else {
      proc readdir {directory} {
         return [glob -directory $directory -tails -nocomplain *]
      }
   }
}

proc getBuildDirectories {{root ""}} {
   set directories [list]

   if {[string length $root] == 0} {
      set root .
      set setPath {
         set path $name
      }
   } else {
      set setPath {
         set path [file join $root $name]
      }
   }

   if {[file exists [file join $root Makefile.in]]} {
      lappend directories $root
   }

   foreach name [readdir $root] {
      eval $setPath
      if {[file isdirectory $path]} {
         lvarcat directories [getBuildDirectories $path]
      }
   }

   return $directories
}

proc generatedFile {file} {
   set name [file tail $file]

   if {[string equal [file extension [file rootname $name]] .auto]} {
      return 1
   }

   if {[regexp {\.tab\.c$} $name]} {
      return 1
   }

   if {[lsearch -exact {brlapi_constants.h} $name] >= 0} {
      return 1
   }

   return 0
}

proc sourceFile {file} {
   if {[file exists [set path "$file.in"]]} {
      return $path
   }

   return ""
}

proc dependencyExists {file} {
   if {[file exists $file]} {
      return 1
   }

   if {[string length [set source [sourceFile $file]]] > 0} {
      if {[file exists $source]} {
         return 1
      }
   }

   return 0
}

proc getDependencies {file} {
   upvar #0 dependencies($file) dependencies

   if {![info exists dependencies]} {
      lappend dependencies $file

      if {[string length [set source [sourceFile $file]]] == 0} {
         set source $file
      }
      set stream [open $source {RDONLY}]

      set includes [list]
      while {[gets $stream line] >= 0} {
         if {[regexp {^ *# *include *"([^"]*)"} $line x header]} {
            lappend includes $header
         }
      }
      close $stream; unset stream

      if {[string equal [set directory [file dirname $file]] .]} {
         set directory ""
      }

      foreach include $includes {
         if {[generatedFile $include]} {
            if {[string first / $include] < 0} {
               set include [file join $directory $include]
            }

            lappend dependencies $include
         } else {
            set found 0
            foreach location [list $directory Programs ""] {
               if {[dependencyExists [set path [file join $location $include]]]} {
                  lvarcat dependencies [lindex [intersect3 $dependencies [getDependencies $path]] 2]
                  set found 1
                  break
               }
            }
            if {!$found} {
               writeProgramMessage "missing dependency: $file includes $include"
            }
         }
      }
   }

   return $dependencies
}

set sourceExtensions {c cc y}
set sourceExtensionsGlob "{[join $sourceExtensions ,]}"

foreach buildDirectory [set buildDirectories [getBuildDirectories]] {
   foreach sourceFile [glob -nocomplain [file join $buildDirectory "*.$sourceExtensionsGlob"]] {
      regsub {^(\./)+} $sourceFile {} sourceFile
      if {![generatedFile $sourceFile]} {
         lappend sourceFiles($buildDirectory) $sourceFile
         getDependencies $sourceFile
      }
   }
}

set generatedDependencies [list]
set objectFiles [list]
set absoluteStream [open absdeps.mk {WRONLY CREAT TRUNC}]
foreach buildDirectory [lsort $buildDirectories] {
   set relativeStream [open [file join $buildDirectory reldeps.mk] {WRONLY CREAT TRUNC}]

   if {[info exists sourceFiles($buildDirectory)]} {
      foreach sourceFile [lsort $sourceFiles($buildDirectory)] {
         switch -exact -- [file extension $sourceFile] {
            ".y" {
               set objectExtension ".tab.c"
            }

            default {
               set objectExtension ".\$O"
            }
         }

         set objectFile "[file rootname $sourceFile]$objectExtension"
         set objectName [file tail $objectFile]

         puts $absoluteStream "# Dependencies for $objectFile:"
         puts $relativeStream "# Dependencies for $objectName:"
         lappend objectFiles $objectFile

         foreach dependency $dependencies($sourceFile) {
            if {[set generated [generatedFile $dependency]]} {
               lappend generatedDependencies $dependency
            }

            if {$generated || ([string length [sourceFile $dependency]] > 0)} {
               set tree BLD
            } else {
               set tree SRC
            }

            puts $absoluteStream "\$(BLD_TOP)$objectFile: \$(${tree}_TOP)$dependency"
            puts -nonewline $relativeStream "$objectName: "

            if {[string equal $buildDirectory [file dirname $dependency]]} {
               if {![string equal $tree BLD]} {
                  puts -nonewline $relativeStream "\$(${tree}_DIR)/"
               }
               puts $relativeStream [file tail $dependency]
            } else {
               puts $relativeStream "\$(${tree}_TOP)$dependency"
            }
         }

         puts $absoluteStream "\tcd \$(@D) && \$(MAKE) \$(@F)"
         puts $absoluteStream ""
         puts $relativeStream ""
      }
   }

   close $relativeStream; unset relativeStream
}

if {![lempty [set generatedDependencies [lindex [intersect3 [lrmdups $generatedDependencies] $objectFiles] 0]]]} {
   puts $absoluteStream "# Generated dependencies:"
   foreach dependency [lsort $generatedDependencies] {
      puts $absoluteStream "\$(BLD_TOP)$dependency:"
      puts $absoluteStream "\tcd \$(@D) && \$(MAKE) \$(@F)"
   }
   puts $absoluteStream ""
}
close $absoluteStream; unset absoluteStream

exit 0
