###############################################################################
migt_version = "V0.0"
###############################################################################
print '.'
print '       Multiple Instance Generation Tool'
print '                -------------'
print '                  MIGT ' + migt_version
print '                -------------'
###############################################################################

###############################################################################
# Created       : 25 Feb 04 - V0.0
# Modified      : 
###############################################################################

import sys
import os
import string
import time

#######################################
# Some constant values
#######################################

Kempty_substution_entry = "['','']"
Kindex_start            = 0
Kindex_increment        = 1

flag_comment            = '#'

flag_key_begin          = '<'
flag_key_end            = '>'

#wait_on_exit            = '' # un-comment this line to wait before exiting

#######################################
# Initialize global variables
#######################################

config_file_path        = './'
config_file_name        = '' 
    
template_file_path      = '' 
template_file_name      = '' 

instances_file_path     = '' 
instances_tot           = 0
instances_file_key      = '' 
instances_file_subst    = eval ( Kempty_substution_entry ) 

substitution_dict       = {}
#substitution_key_tot    = 0  not used

try_adjust_tabulation   = 'N' # needs to be enabled with MIGT_tabulation()
adjust_first_word_begin = 0
adjust_second_word_begin= 6
adjust_third_word_begin = 30
adjust_comment_begin    = 60
adjust_max_length       = 100

############################################################################
# close all possibly opened files on Exit
# Make exit actions a function so that we can use it for aborting from anywhere
############################################################################

def Done():
#----------

    print '.'

    if ( globals().has_key( 'template_file_object' ) ) :
        template_file_object.close ()

    if ( globals().has_key( 'instance_file_object' ) ) :
        instance_file_object.close ()

    print '.'
    print 'Done.'

    if ( globals().has_key( 'wait_on_exit' ) ) : raw_input ( '<CR>' )

    sys.exit()


#######################################
# Define Functions for User Configuration File
#######################################

def MIGT_input_template ( path='', name='' ) :
   #------------------- 

    global template_file_path 
    global template_file_name 

    if ( path <> '' ) :
        template_file_path = path 
        if ( template_file_path [-1] != '/' ) : # enforce a trailing slash
            template_file_path = template_file_path  + '/'
        #print "template_file_path       =" + template_file_path
    
    if ( name <> '' ) :
        template_file_name = name 
        if ( template_file_name [0] == '/' ) : # enforce no no leading slash
            template_file_name = template_file_name [1:]
        #print "template_file_name       =" + template_file_name 
    


def MIGT_output_instances ( path='', total='', key_is='', start_at='', increment_by='' ) :
   #---------------------

    global instances_file_path  
    global instances_tot 
    global instances_file_key
    global instances_file_subst 

    if ( path <> '' ) :
        instances_file_path  = path  
        if ( instances_file_path [-1] != '/' ) : # enforce a trailing slash
            instances_file_path = instances_file_path + '/'
        #print "instances_file_path     =" + instances_file_path  

    if ( total <> '' ) :
        instances_tot = int(total)
        #print "instances_tot            =" + str(instances_tot)

    if ( key_is <> '' ) :
        instances_file_key = key_is 
        #print "key_is                   =" + instances_file_key

    if ( start_at <> '' ) :
        instances_file_subst[Kindex_start] = start_at
        #print "start_at                 =" + instances_file_subst[Kindex_start]

    if ( increment_by <> '' ) :
        instances_file_subst[Kindex_increment] = increment_by
        #print "increment_by             =" + instances_file_subst[Kindex_increment]


def MIGT_substitute ( key_is, start_at, increment_by ) :
   #-------------------------------

    global substitution_dict 
    #global substitution_key_tot

    key = flag_key_begin            \
        + string.lower ( key_is )   \
        + flag_key_end
        
    # make sure this key wasn't defined before
    if ( substitution_dict.has_key ( key ) ) :

        print '.'
        print "MIGT> Error: multiple definition found for key <" + key_is + '>'
        Done()
            
    #substitution_key_tot = substitution_key_tot + 1
    substitution_dict [key] = eval ( Kempty_substution_entry ) 
    substitution_dict [key] [Kindex_start]       = start_at
    substitution_dict [key] [Kindex_increment]   = increment_by 
    
    #print "New key_is ="    + key_is \
    #    + " start_at ="     + substitution_dict [key] [Kindex_start] \
    #    + " increment_by =" + substitution_dict [key] [Kindex_increment] 



def MIGT_tabulation ( first_word='', second_word='', third_word='', trail_comment='', max_length='' ) :
   #-------------------------------

    global try_adjust_tabulation 
    global adjust_first_word_begin 
    global adjust_second_word_begin
    global adjust_third_word_begin 
    global adjust_comment_begin    
    global adjust_max_length       

    try_adjust_tabulation   = 'Y' 

    if ( first_word <> '' ) :
        adjust_first_word_begin = int(first_word)
        print "adjust_first_word_begin  =" + adjust_first_word_begin 
    
    if ( second_word <> '' ) :
        adjust_second_word_begin = int(second_word)
        print "adjust_second_word_begin =" + adjust_second_word_begin 
    
    if ( third_word <> '' ) :
        adjust_third_word_begin = int(third_word)
        print "adjust_third_word_begin  =" + adjust_third_word_begin 
    
    if ( trail_comment <> '' ) :
        adjust_comment_begin = int(trail_comment)
        print "adjust_comment_begin  =" + adjust_comment_begin 
    
    if ( max_length <> '' ) :
        adjust_max_length = int(max_length)
        print "adjust_max_length  =" + adjust_max_length
    



#######################################
# Implement recursive key substitution function
#######################################

def dict_substitution ( this_template_line, template_line_num, instance_num ) :

    subst_template_line = this_template_line
    
    #print "Processing =", subst_template_line, 

    # We will look for the first key field of the line
    first_field_begin = string.find ( subst_template_line, flag_key_begin ) 

    # make sure there was a key field of the line
    if ( first_field_begin >=  0 ) :
    
        #locate the key field
        first_field_end = string.find ( subst_template_line, flag_key_end ) + 1
        first_field_name = subst_template_line[ first_field_begin : first_field_end ]

        # make sure we found a legal key field i.e. with a closing bracket
        if ( first_field_name < 0 ) : 

            print '.'
            print "MIGT> Error: missing closing key delimiter" + first_field_name  \
                            + " at template line " + str(template_line_num)
            Done()
            
        #make sure we have an entry for this key
        if ( not ( substitution_dict.has_key ( string.lower(first_field_name) ) ) ) :

            print '.'
            print "MIGT> Error: no key defined for " + first_field_name  \
                            + " at template line " + str(template_line_num)
            Done()
           
        #print 'key found ' + first_field_name  + ' at template line ' + str(template_line_num)
         
        # perform the substitution on this key
        first_field_name = string.lower(first_field_name) 
        subst_template_line = this_template_line[:first_field_begin]                            \
                           + str ( int( substitution_dict[first_field_name][Kindex_start] )     \
                                 + int( substitution_dict[first_field_name][Kindex_increment] ) \
                                 * instance_num )                                               \
                           + subst_template_line[first_field_end:]            

        #print "Processed  =", subst_template_line,
            
        # see if there are more keys left on this line, and do recursive call
        first_field_begin = string.find ( subst_template_line, flag_key_begin ) 
        if ( first_field_begin >=  0 ) :
            subst_template_line = dict_substitution ( subst_template_line,  \
                                                      template_line_num,    \
                                                      instance_num )       

    return subst_template_line


#######################################
# Implement line tabulation embelishment
#######################################

def adjust_tabulation ( this_template_line, template_line_num ) :

    
    #print "Processing =", this_template_line , 

    not_yet_adjusted = this_template_line
    already_adjusted = ''

    #don't adjust empty lines or lines *starting* with a comment flag
    if ( ( len ( string.lstrip( this_template_line ) ) <> 0 ) \
     and ( this_template_line[0] <> flag_comment ) ) :

        #get rid of tab characters
        not_yet_adjusted = string.expandtabs ( not_yet_adjusted )

        #drop leading spaces and guarantee at least one space after first word (to avoid a special case)
        not_yet_adjusted = string.lstrip ( not_yet_adjusted ) + ' '
        #print 'lstrip=' + not_yet_adjusted ,
        #print "e.g.  =NET  'BLS_1_EM_N'     R10-2   C10-2     # Complement Input Pin"
        
        #also add a temporary comment flag if needed (to avoid special cases further on)
        if ( string.find ( not_yet_adjusted, flag_comment ) < 0 ) :
            not_yet_adjusted = not_yet_adjusted + flag_comment 
            added_comment = 'Y'
        else : 
            added_comment = 'N'
            
        if ( not_yet_adjusted [0] <> flag_comment ) :

            #find end of first word
            first_space_begin = string.find ( not_yet_adjusted, ' ' )

            #we can now build the beginning of the adjusted line up to the begining of the first word
            already_adjusted = already_adjusted                                             \
                             + ( adjust_first_word_begin - len(already_adjusted) -1 ) * ' ' \
                             + not_yet_adjusted [:first_space_begin+1]
            #print 'adjust=' + already_adjusted + '"'
            #print "e.g.  =NET "

            #recover what's left of end of line
            not_yet_adjusted = not_yet_adjusted [first_space_begin+1:]
            #print 'remain=' + not_yet_adjusted ,
            #print "e.g.  = 'BLS_1_EM_N'     R10-2   C10-2     # Complement Input Pin"

            #drop leading spaces 
            not_yet_adjusted = string.lstrip ( not_yet_adjusted ) 
            #print 'lstrip=' + not_yet_adjusted ,
            #print "e.g.  ='BLS_1_EM_N'     R10-2   C10-2     # Complement Input Pin"

            if ( not_yet_adjusted [0] <> flag_comment ) :

                #find end of second word (still ok if there isn't one)
                first_space_begin = string.find ( not_yet_adjusted, ' ' )

                #we can now build the beginning of the adjusted line up to the begining of the second word
                already_adjusted = already_adjusted                                              \
                                 + ( adjust_second_word_begin - len(already_adjusted) -1 ) * ' ' \
                                 + not_yet_adjusted [:first_space_begin+1]
                #print 'adjust=' + already_adjusted + '"'
                #print "e.g.  =NET  'BLS_1_EM_N' "

                #recover what's left of end of line
                not_yet_adjusted = not_yet_adjusted [first_space_begin+1:]
                #print 'remain=' + not_yet_adjusted ,
                #print "e.g.  =    R10-2   C10-2     # Complement Input Pin"

                #drop leading spaces 
                not_yet_adjusted = string.lstrip ( not_yet_adjusted ) 

                #print 'lstrip=' + not_yet_adjusted ,
                #print "e.g.  =R10-2   C10-2     # Complement Input Pin"

        #find beginning of comment
        comment_begin = string.find ( not_yet_adjusted, flag_comment )

        #we can now build the beginning of the adjusted line up to the begining of comment
        already_adjusted = already_adjusted                                             \
                         + ( adjust_third_word_begin - len(already_adjusted) -1 ) * ' ' \
                         + not_yet_adjusted [:comment_begin]
        #print 'adjust=' + already_adjusted + '"'
        #print "e.g.  =NET  'BLS_1_EM_N'       R10-2   C10-2     "

        #trim spaces at end of line before we start adjusting comment position
        already_adjusted = string.rstrip ( already_adjusted ) + ' ' 

        #recover the comment field which now is the end of line
        comment_field = not_yet_adjusted [comment_begin:]
        #print 'remain=' + comment_field ,
        #print "e.g.  =# Complement Input Pin"

        #we can now build the whole adjusted line 
        already_adjusted = already_adjusted                                             \
                         + ( adjust_comment_begin - len(already_adjusted) -1 ) * ' '    \
                         + comment_field 
        #print 'adjust=' + already_adjusted ,
        #print "e.g.  =NET  'BLS_1_EM_N'       R10-2   C10-2            # Complement Input Pin"

        #remove temporary comment flag that we may have added
        if ( added_comment == 'Y' ) :
            already_adjusted = already_adjusted [:-1] 
        
        #trim extra spaces  at end of line (possibly including some we added)
        already_adjusted = string.rstrip ( already_adjusted ) 

       
    else : 
    
        already_adjusted = this_template_line
        #print "Skipping =", already_adjusted , 


    #sanity check: all characters in input string should still be in adjusted string
    if ( string.replace ( string.expandtabs ( this_template_line ), ' ', '' ) 
      <> string.replace ( already_adjusted, ' ', '' ) ) :
        print '.'
        print "MIGT> Error: tabulation adjustment error at template line " + str(template_line_num)
        print "Original String>" + this_template_line
        print "Adjusted String>" + already_adjusted
        Done()
      

    return already_adjusted 

#######################################
# Find configuration file, if it wasn't passed as argument
#######################################

if ( len (sys.argv) > 1 ) :

    config_file = sys.argv[1]

else :

    #Ask for configuration file we are supposed to follow
    #----------------------------------------------

    print '.'
    print 'What is the configuration file ? '
    config_file = raw_input ( 'Enter File Name : ' )


#Separate path and directory 
#----------------------------

config_file_split = os.path.split ( config_file )
config_file_name  = config_file_split[1]
if ( config_file_split[0] != '' ) :
    config_file_path = config_file_split[0] + '/'  

# get a listing of the apropriate directory
#------------------------------------------

config_file_path_list = os.listdir( config_file_path )

#check for config file existence
#-------------------------------
if ( config_file_name   not in  config_file_path_list ) :
    print '.'
    print "MIGT> Error: Could not locate file <" + config_file_name + '>'
    Done()

#######################################
# Execute configuration file
#######################################

# This calls back the configuration file
# (which was passed as argument, or 
# (which had called this script in the first place,
#  or which was explicitely specified just above)
# and execute each line as part of this python script 

print '.'
print 'Reading Configuration File <' + config_file + '>'

execfile ( config_file )

print '.'
print 'Done Reading Configuration File <' + config_file + '>'


#######################################
# Set values left unspecified to their defined default
#######################################

if ( template_file_path     == '' ) :
     template_file_path     = config_file_path

if ( instances_file_path    == '' ) :
     instances_file_path    = template_file_path

if ( instances_tot          == '' ) :
     instances_tot          = 1

if ( instances_file_key     == '' ) :
     instances_file_key     = "template"

if ( instances_file_subst[Kindex_start] == '' ) :
     instances_file_subst[Kindex_start] = "1"

if ( instances_file_subst[Kindex_increment] == '' ) :
     instances_file_subst[Kindex_increment] = "1"

#######################################
# Locate and Read Template File
#######################################

# verify we were given a template file name
if ( template_file_name  == '' ) :

    print '.'
    print "MIGT> Error: no template file specified "
    Done()

# verify now that we will be able to generate output file names
if ( string.find ( template_file_name, instances_file_key ) < 0 ) :

    print '.'
    print "MIGT> Error: template file name <" + template_file_name + '>' \
         + " does not include key <" + instances_file_key + '>'
    Done()

#read and remember the template file
print '.'
print 'Reading template file <' + template_file_name + '>'

try: 

    template_file_object = open ( template_file_path+template_file_name, 'r' )
    template_lines       = template_file_object.readlines ()
    template_file_object.close ()

except:

    print '.'
    print "MIGT> Error: could not find or read template file"
    Done()


#######################################
# Generate Output Instance File(s)
#######################################

#There will be several output instance files
for instance_num in range ( instances_tot ) : 

    # generate the name of this output instance file
    instance_file_key_begin = string.find ( template_file_name, instances_file_key ) 
    instance_file_key_end   = instance_file_key_begin + len(instances_file_key)
    instance_file_name = template_file_name [:instance_file_key_begin]          \
                       + str ( int( instances_file_subst[Kindex_start] )        \
                             + int( instances_file_subst[Kindex_increment] )    \
                             * instance_num )                                   \
                       + template_file_name [instance_file_key_end:]            
    
    print '.'
    print 'Generating output instance file #', (instance_num+1), '/', instances_tot, \
          ':', instance_file_name 

    # create new output instance file
    instance_file_object = open ( instances_file_path+instance_file_name, 'w' )

    # write header into output instance file
    instance_file_object.write ( flag_comment + " MIGT>----------------------------------------------\n" )
    instance_file_object.write ( flag_comment + " MIGT> " + migt_version )
    instance_file_object.write (                " -- " + time.asctime ( time.localtime( time.time() ) ) + '\n' )
    instance_file_object.write ( flag_comment + " MIGT> begin substituting from <" + template_file_name + '>' ) 
    instance_file_object.write (                " -- instance #" + str(instance_num+1) + '/' + str(instances_tot) + '\n' )
    instance_file_object.write ( flag_comment + " MIGT>----------------------------------------------\n" )

    # go through all input template file lines
    for template_line_num in range ( len(template_lines) ) :

        #get content of line (not the <CR>)
        this_template_line = string.rstrip( template_lines[template_line_num] )

        # see if the input template file line needs substitution
        if ( string.find ( this_template_line , flag_key_begin ) >=  0 ) :
            #There is at least one key on this line
            this_template_line = dict_substitution ( this_template_line, \
                                                     template_line_num,  \
                                                     instance_num ) 
        # adjust line tabulation 
        if ( try_adjust_tabulation <> 'N' ) :
            this_template_line = adjust_tabulation ( this_template_line, \
                                                     template_line_num ) 
                                                 
        # copy the line
        instance_file_object.write ( this_template_line + '\n' ) 

    instance_file_object.write ( flag_comment + " MIGT>----------------------------------------------\n" )
    instance_file_object.write ( flag_comment + " MIGT> done substituting from <" + template_file_name + '>\n' )
    
    instance_file_object.close () 
    
    
Done () 
