#
# $RCSfile: modutil.itcl,v $ --
#
#      This file contains utilities functions used/shared by 
#      various modules, such as auxiliary functions for 
#      readfilters, ...
#
# Copyright (c) 2004  Anton Kokalj   Email: tone.kokalj@ijs.si
#
#
# This file is distributed under the terms of the GNU General Public
# License. See the file `COPYING' in the root directory of the present
# distribution, or http://www.gnu.org/copyleft/gpl.txt .
#
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
# ANTON KOKALJ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
#
# $Id: modutil.itcl,v 1.4 2008-02-15 17:30:04 kokalj Exp $ 
#

namespace eval ::pwscf::readFilter {}


#****f* module/Functions
# SYNOPSIS
proc ::pwscf::readFilter::replaceFlag {line reference args} {    
    # PURPOSE
    # Handle properly multiple flags with the same meaning, that is,
    # for example: map 'methfessel-paxton', or 'm-p', or 'mp' --> 'methfessel-paxton'
    # EXAMPLE
    #  readFilter::replaceFlag $line methfessel-paxton m-p mp 
    # ARGUMENTS
    # * line -- line of input to replace the flag to a reference value
    # * reference -- reference value for flag (i.e. 'methfessel-paxton')
    # * args -- possible other value of flag with the same meaning ('m-p' and 'mp')
    # SOURCE

    set _line $line
    foreach flag $args {
        if { [regexp \[\"'\]${flag}\[\"'\] $line] } {
	    regsub $flag $line $reference _line
	    break
	}
    }
    return $_line
}
#******


#****f* module/Functions
# SYNOPSIS
proc ::pwscf::readFilter::replaceVarFlag {line var optsList} {
    # PURPOSE
    # Handle properly multiple flags with the same meaning, that is,
    # for example: map 'methfessel-paxton', or 'm-p', or 'mp' --> 'methfessel-paxton'
    # EXAMPLE
    #  readFilter::replaceVarFlag $line $var optList
    # ARGUMENTS
    # * line -- line of input to replace the flag to a reference value
    # * var  -- name of the variable for which to replace a flag to a reference flag
    # * optList -- kind of double-list of options (the first option-value is taken as a reference), i.e.:
    #              set optList {
    #		           {'gaussian' 'gauss'}
    #		           {'methfessel-paxton' 'm-p' 'mp'}
    #		           {'marzari-vanderbilt' 'cold' 'm-v' 'mv'}
    #		           {'fermi-dirac' 'f-d' 'fd'}
    #		   }
    # SOURCE

    if { ! [string match *${var}* $line] } { return $line }

    set substituted 0

    set spl [split $line =] 
    set nl  [llength $spl]
    
    set new_spl [lindex $spl 0]
    
    for {set i 1} {$i < $nl} {incr i} {

	set var_   [string trim [lindex [lindex $spl [expr $i - 1]] end] ,]
	set value_ [lindex $spl  $i]
	
	if { $var_ == $var } {

	    # ok we find the proper variable; replace its flag with the reference value

	    foreach opts $optsList {
		#
		set reference [lindex $opts 0]
		set flags     [lrange $opts 1 end]
		    
		foreach flag $flags {
		    #
		    if { [regexp ${flag} $value_] } {
			#
			set value_ [regsub $flag $value_ $reference]
			set substituted 1
			break
		    }
		}
	    }
	}	
	lappend new_spl $value_
    }
    
    if { $substituted } {
	set line [join $new_spl =]
    }

    return $line
}
#******


proc ::pwscf::readFilter::logicalFlag {line} {
    # PURPOSE
    # Convert all variants of .TRUE. .true. .T. .t. ---> .true.
    # and all variants of .FALSE. .false. .F. .f. ---> .false.
    foreach true {true t} {
	set regexp "= {0,}\[.\]${true}\[.\]"
	if { [regexp -nocase -- $regexp $line] } {
	    regsub -all -nocase -- $regexp $line {= .true.} line
	}
    }
    foreach false {false f} {
	set regexp "= {0,}\[.\]${false}\[.\]"
	if { [regexp -nocase -- $regexp $line] } {
	    regsub -all -nocase -- $regexp $line {= .false.} line
	}
    }
    return $line
}

proc ::pwscf::readFilter::purifyCardLine {cardLine} {
    # PURPOSE
    # Purify the card-line, which can hold something like: "ATOMIC_POSITIONS { alat }",
    # but we want: ""ATOMIC_POSITIONS alat"
    set _part1 [lindex $cardLine 0]
    set _part2 [string tolower [string trim [lrange $cardLine 1 end] "{}()"]]
    return "${_part1} ${_part2}"
}


proc ::pwscf::readFilter::amassIndex {line {maxIndex 0}} { 
    # PURPOSE
    # search for the largest index of amass()-dimension in $line and
    # return the largest index found; $line is a line of input
    foreach field [split $line =] {
	if { [regexp -nocase {amass\([0-9]+\)} $field matchVar] } {
	    regexp {[0-9]+} $matchVar index
	    if { $index > $maxIndex } { 
		set maxIndex $index 
	    }
	}
    }    
    return $maxIndex
}


# consider these variables:
# Variable: exist_namelist -- namelists that must exist
# Variable: exist_cards    -- cards that must exist

proc ::pwscf::readFilter::default {moduleObj channel {filterMode logical} {replList {}}} {
    # PURPOSE
    # Default readfilter, which can have several possible modes:    
    #
    # Variable: filterMode
    # Description:
    # mode=amass   search for largest amass(*) index and assign the
    #              ntyp variables
    #
    # mode=logical repleace all possible values of Fortran logical
    #              variables with .true. or .false.
    #
    # The value of filterMode is cumulative, i.e., "amass logical" is
    # allowed
    #    
    # Variable: replList
    # Description: replList are double list of the form
    # { {replaceFromList1  replaceToItem1} {replaceFromList2  replaceToItem2} ... }
    # 
    set amass   0
    set logical 0
    if { [string match "*amass*"   $filterMode] } { set amass   1 }
    if { [string match "*logical*" $filterMode] } { set logical 1 }

    set maxIndex 0
    set output   {}
    while { ! [eof $channel] } {
	gets $channel _line
	if { $amass } {
	    set maxIndex [amassIndex $_line $maxIndex]
	}
	if { $logical } {
	    set _line    [logicalFlag $_line]       
	}
	if { $replList != "" } {
	    foreach repl $replList {
		set replFromList [lindex $repl 0]
		set replToItem   [lindex $repl 1]
		set _line [eval {replaceFlag $_line $replToItem} $replFromList]
		#foreach replItem $replFromList {
		#    set _line [replaceFlag $_line $replToItem $replItem]
		#}
	    }
	}
	append output $_line\n
    }
    if { $maxIndex > 0 && $amass } {
	$moduleObj varset ntyp -value $maxIndex
    }
    # close the old channel
    close $channel

    # open a new channel (i.e. temporary file) and write the 
    # $output to it
    set tmpfile    [::tclu::tempFile name input]
    set newChannel [open $tmpfile w+]
    puts  $newChannel $output
    flush $newChannel

    # rewind the newChannel
    seek $newChannel 0 start
    return $newChannel
}


proc ::pwscf::readFilter::findNamelists {moduleObj channel namelists errMsgVar} {
    upvar $errMsgVar errMsg
    # PURPOSE: 
    # Find if given namelists exist in the input file.
    #
    # Variable: namelists -- list of namelist (namelists names should be without "&" prefix)
    #
    #           errMsgVar -- contain the error message on output
    #
    # Returns:
    #          1 -- if namelist is present
    #          0 -- if namelist is not present

    # init ...
    set _errMsg "- the following namelists are missing:\n"
    foreach name $namelists {
	set exist($name) 0
    }

    # start from the beginning of input file
    seek $channel 0 start

    # search the file 
    while { ! [eof $channel] } {
	gets $channel _line
	foreach name $namelists {
	    if { [string match -nocase "*&${name}*" $_line] } {		
		set exist($name) 1
	    }
	}
    }
    
    # file position back to the start of file
    seek $channel 0 start

    # post-processing ...
    set result 1
    foreach name $namelists {
	if { $exist($name) == 0 } {
	    # should we make some info about the missing namelists	    
	    append _errMsg "    &${name}\n"
	    #lappend pwscf($moduleObj,missing.namelists) "&$name"
	    set result 0
	}
    }
    if { ! $result } {
	set errMsg $_errMsg
    }
    return $result
}


proc ::pwscf::readFilter::findCards {moduleObj channel cardlists errMsgVar {nocase 0}} {
    upvar $errMsgVar errMsg
    # PURPOSE
    # Find if given cards exist in the input file.
    #
    # Variable: cardlist  -- list of cards to search for
    #
    #           errMsgVar -- contain the error message on output
    #
    # Returns:
    #          1 -- if namelist is present
    #          0 -- if namelist is not present

    # init ...
    set _errMsg "- the following cards are missing:\n"
    foreach name $cardlists {
	set exist($name) 0
    }

    # start from the beginning of input file
    seek $channel 0 start

    # search the file 
    while { ! [eof $channel] } {
	gets $channel _line
	foreach name $cardlists {
	    if { [::tclu::stringMatch "*${name}*" $_line $nocase] } {
		set exist($name) 1
	    }
	}
    }
    
    # file position back to the start of file
    seek $channel 0 start

    # post-processing ...
    set result 1
    foreach name $cardlists {
	if { $exist($name) == 0 } {
	    # should we make some info about the missing cardlists
	    append _errMsg "    ${name}\n"
	    #lappend pwscf($moduleObj,missing.cards) "$name"
	    set result 0
	}
    }
    if { ! $result } {
	set errMsg $_errMsg
    }
    return $result
}


proc ::pwscf::readFilter::findNamelistsAndCards {moduleObj channel formatName namelists cards errMsgVar {cards_nocase 0}} {
    upvar $errMsgVar errMsg
    #
    # Purpose: find if given namelists and cards exist in the input file

    set nml_status  [::pwscf::readFilter::findNamelists $moduleObj $channel $namelists nml_errMsg]
    set card_status [::pwscf::readFilter::findCards $moduleObj $channel $cards card_errMsg $cards_nocase]    

    if { $nml_status == 0 || $card_status == 0 } {
	#set errMsg "selected file is not a pw.x formatted or contains severe syntax errors.\n\nDiagnosis:\n"
	if { $nml_status == 0  } { 
	    append errMsg "  $nml_errMsg\n" 
	}
	if { $card_status == 0 } { 
	    append errMsg "  $card_errMsg" 
	}
	return 0
    }
    return 1
}


proc ::pwscf::selectFileRoot {moduleObj variable} {
    # PURPOSE
    # Select the root of a filename and quote it.
    # Example: file.ext --> 'file'
    variable pwscf

    set _dir [string trim [$moduleObj varvalue outdir] "'"]
    
    if { [file isdirectory $_dir] } {
	set dir $_dir
    } elseif { [info exists pwscf($moduleObj,LASTDIR,punchfile)] } {
	set dir $pwscf($moduleObj,LASTDIR,punchfile)
    } else {
	set dir $pwscf(PWD)
    }        
    
    set file [tk_getOpenFile \
		  -initialdir $dir \
		  -title      "Select a Punch File"]
    if { $file == "" } {
	return
    }
    set pwscf($moduleObj,LASTDIR,punchfile) [file dirname $file]
    
    set file '[file tail [file rootname $file]]'
    $moduleObj varset $variable -value $file    
}
