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

# COLL.TCL, collection management procedures
# Copyright (C) 2016-2021 Xavier Delaruelle
#
# This program is free software: you can redistribute it and/or modify
# it 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

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

# build list of what to undo then do to move from an initial list to a target
# list, eventually checking element presence in extra from/to lists
proc getMovementBetweenList {from to {extfrom {}} {extto {}} {cmp eq}} {
   reportDebug "from($from) to($to) with extfrom($extfrom) extto($extto)"

   set undo {}
   set do {}

   # determine what element to undo then do
   # to restore a target list from a current list
   # with preservation of the element order
   set imax [if {[llength $to] > [llength $from]} {llength $to} {llength\
      $from}]
   set list_equal 1
   for {set i 0} {$i < $imax} {incr i} {
      set to_obj [lindex $to $i]
      set from_obj [lindex $from $i]
      # check from/to element presence in extra from/to list
      set in_extfrom [expr {$from_obj in $extfrom}]
      set in_extto [expr {$to_obj in $extto}]
      # are elts the sames and are both part of or missing from extra lists
      # when comparing modules, ask comparison against loaded module
      # alternative and simplified names (modEq will also compare variants)
      if {($cmp eq {modeq} && ![modEq $to_obj $from_obj equal 1 3 1]) ||\
         ($cmp eq {eq} && $to_obj ne $from_obj) || $in_extfrom != $in_extto} {
         set list_equal 0
      }
      if {$list_equal == 0} {
         if {$to_obj ne {}} {
            lappend do $to_obj
         }
         if {$from_obj ne {}} {
            lappend undo $from_obj
         }
      }
   }

   return [list $undo $do]
}

# build list of currently loaded modules where modulename is registered minus
# module version if loaded version is the default one
proc getSimplifiedLoadedModuleList {} {
   set curr_mod_list {}
   set curr_nuasked_list {}
   set modpathlist [getModulePathList]
   foreach mod [getLoadedModuleList] {
      set altandsimplist [getLoadedAltAndSimplifiedName $mod]

      set parentmod [file dirname $mod]
      set simplemod $mod
      # simplify to parent name as long as it is found in simplified name list
      while {$parentmod ne {.}} {
         if {$parentmod in $altandsimplist} {
            set simplemod $parentmod
            set parentmod [file dirname $parentmod]
         } else {
            set parentmod .
         }
      }

      # add each module specification as list to correctly enclose spaces in
      # module name or variant name or value
      set simplemodvr [list $simplemod {*}[getVariantList $mod 5 1]]
      lappend curr_mod_list $simplemodvr
      # record not user asked module list in simplified version form
      if {[isModuleTagged $mod auto-loaded 1]} {
         lappend curr_nuasked_list $simplemodvr
      }
   }

   return [list $curr_mod_list $curr_nuasked_list]
}

# return saved collections found in user directory which corresponds to
# enabled collection target if any set.
proc findCollections {} {
   if {[info exists ::env(HOME)]} {
      set coll_search $::env(HOME)/.module/*
   } else {
      reportErrorAndExit {HOME not defined}
   }

   # find saved collections (matching target suffix)
   # a target is a domain on which a collection is only valid.
   # when a target is set, only the collections made for that target
   # will be available to list and restore, and saving will register
   # the target footprint
   set colltarget [getConf collection_target]
   if {$colltarget ne {}} {
      append coll_search .$colltarget
   }

   # glob excludes by default files starting with "."
   if {[catch {set coll_list [glob -nocomplain $coll_search]} errMsg]} {
      reportErrorAndExit "Cannot access collection directory.\n$errMsg"
   }

   return $coll_list
}

# get filename corresponding to collection name provided as argument.
# name provided may already be a file name. collection description name
# (with target info if any) is returned along with collection filename
proc getCollectionFilename {coll} {
   # initialize description with collection name
   set colldesc $coll

   if {$coll eq {}} {
      reportErrorAndExit [getEmptyNameMsg collection]
   # is collection a filepath
   } elseif {[string first / $coll] > -1} {
      # collection target has no influence when
      # collection is specified as a filepath
      set collfile $coll
   # elsewhere collection is a name
   } elseif {[info exists ::env(HOME)]} {
      set collfile $::env(HOME)/.module/$coll
      # if a target is set, append the suffix corresponding
      # to this target to the collection file name
      set colltarget [getConf collection_target]
      if {$colltarget ne {}} {
         append collfile .$colltarget
         # add knowledge of collection target on description
         append colldesc " (for target \"$colltarget\")"
      }
   } else {
      reportErrorAndExit {HOME not defined}
   }

   return [list $collfile $colldesc]
}

# generate collection content based on provided path and module lists
proc formatCollectionContent {path_list mod_list nuasked_list {sgr 0}} {
   set content {}

   # graphically enhance module command if asked
   set modcmd [expr {$sgr ? [sgr cm module] : {module}}]

   # start collection content with modulepaths
   foreach path $path_list {
      # enclose path if space character found in it
      if {[string first { } $path] != -1} {
         set path "{$path}"
      }
      # 'module use' prepends paths by default so we clarify
      # path order here with --append flag
      append content "$modcmd use --append $path" \n
   }

   # then add modules
   foreach mod $mod_list {
      # mark modules not asked by user to restore the user asked state
      set opt [expr {$mod in $nuasked_list ? {--notuasked } : {}}]
      # no need to specifically enclose module specification if space char
      # used in it as $mod is a list so elements including space will be
      # automatically enclosed
      append content "$modcmd load $opt$mod" \n
   }

   return $content
}

# read given collection file and return the path and module lists it defines
proc readCollectionContent {collfile colldesc} {
   # init lists (maybe coll does not set mod to load)
   set path_list {}
   set mod_list {}
   set nuasked_list {}

   # read file
   if {[catch {
      set fdata [split [readFile $collfile] \n]
   } errMsg ]} {
      reportErrorAndExit "Collection $colldesc cannot be read.\n$errMsg"
   }

   # analyze collection content
   foreach fline $fdata {
      if {[regexp {module use (.*)$} $fline match patharg] == 1} {
         # paths are appended by default
         set stuff_path append
         # manage multiple paths and path options specified on single line,
         # for instance "module use --append path1 path2 path3", with list
         # representation of patharg (which handles quoted elements containing
         # space in their name)
         foreach path $patharg {
            # following path is asked to be appended
            if {($path eq {--append}) || ($path eq {-a})\
               || ($path eq {-append})} {
               set stuff_path append
            # following path is asked to be prepended
            # collection generated with 'save' does not prepend
            } elseif {($path eq {--prepend}) || ($path eq {-p})\
               || ($path eq {-prepend})} {
               set stuff_path prepend
            } else {
               # ensure given path is absolute to be able to correctly
               # compare with paths registered in MODULEPATH
               set path [getAbsolutePath $path]
               # add path to end of list
               if {$stuff_path eq {append}} {
                  lappend path_list $path
               # insert path to first position
               } else {
                  set path_list [linsert $path_list 0 $path]
               }
            }
         }
      } elseif {[regexp {module load (.*)$} $fline match modarg] == 1} {
         # remove collection-specific flags in specification
         set cleanlist [lsearch -all -inline -not -exact $modarg\
            --notuasked]
         # parse module specification to distinguish between module + variant
         # specif and multiple modules specified on a single line
         set parsedlist [parseModuleSpecification 0 {*}$cleanlist]
         if {[llength $modarg] != [llength $cleanlist]} {
            foreach parsed $parsedlist {
               # add module name version and variant spec to the list
               lappend nuasked_list $parsed
            }
         }
         lappend mod_list {*}$parsedlist
      }
   }
   return [list $path_list $mod_list $nuasked_list]
}

# ;;; Local Variables: ***
# ;;; mode:tcl ***
# ;;; End: ***
# vim:set tabstop=3 shiftwidth=3 expandtab autoindent:
