#!/usr/bin/env python

import birchenv

#optparse is deprecated in favor of argparse as of Python 2.7. However,
# since 2.7 is not always present on many systems, at this writing,
# it is safer to stick with optparse for now. It should be easy
# to change later, since the syntax is very similar between argparse and optparse.
from optparse import OptionParser

import os
import os.path
import stat
import subprocess
import sys
import re
import shutil


'''
BLHelper.py - Set environment variables for BioLegato Helper Applications

Synopsis: BLHelper.py --install --birchdir directory [--platform platform]
          BLHelper.py --update --birchdir directory [--platform platform]
          BLHelper.py --setvar variable_name=value --birchdir directory [--platform platform]
  
@modified: June  18, 2015
@author: Brian Fristensky
@contact: frist@cc.umanitoba.ca  
'''

blib = os.environ.get("BIRCHPYLIB")
sys.path.append(blib)

from birchlib import Birchmod


PROGRAM = "BLHelper.py : "
USAGE = "\n\tUSAGE: BLHelper.py --install --platform platform --birchdir directory" +\
   "\n\t\tBLHelper.py --update --platform platform --birchdir directory" + \
   "\n\t\tBLHelper.py --setvar variable_name=value --platform platform --birchdir directory"

DEBUG = True
if DEBUG :
    print('Debugging mode on')

BM = Birchmod(PROGRAM, USAGE)

BLplatforms = ['linux-intel','linux-x86_64','osx-x86_64','solaris-sparc','solaris-amd64']
BLvariables = ['BL_Browser', 'BL_PDFViewer', 'BL_PSViewer',  'BL_TextEditor', 'BL_Terminal', 'BL_ImageViewer', 'BL_Document', 'BL_Spreadsheet']


# - - - - - - - - - - - - - Utility classes - - - - - - - - - - - - - - - - -
def chmod_ar(filename):
	if os.path.exists(filename):
		st = os.stat(filename)
		os.chmod(filename, st.st_mode | stat.S_IREAD \
			| stat.S_IRGRP | stat.S_IROTH)

		
def chmod_arx(filename):
	if os.path.exists(filename):
		st = os.stat(filename)
		os.chmod(filename, st.st_mode | stat.S_IEXEC | stat.S_IREAD \
			| stat.S_IXGRP | stat.S_IRGRP | stat.S_IXOTH \
			| stat.S_IROTH)

def BinaryExists(Command,Platform):
    """
            Read choice tuples from BL_variable_<platform>.list file.

            Commands could either be in the PATH, which is handled by the
            'which' command. 

            Also on MacOSX, the command may be a statement for running an Application,
            which takes the form

            open -a application_name
    """
    OKAY = False
    
    # First, we check to see if the program is in the PATH.
    # Just in case we get a command line with arguments as input, we take
    # only the first non-blank token
    Program = Command.split(" ")[0]
    OUTPUT = ""
    p = subprocess.Popen(["which", Program],stdout=subprocess.PIPE)
    OUTPUT = p.communicate()[0]
    p.wait()        
    expr = '^(/.+){1,}'
    cexpr = re.compile(expr)

    if re.match(cexpr,OUTPUT) :
        OKAY = True

    # On Mac OSX, we also have to check for Applications with in the /Applications
    # directory with the .app extension
    if not OKAY :
        if Platform == 'osx-x86_64' :
             I = Command.rfind('open -a ')
             AppName = Command[I:]
             AppDir= '/Applications'
             AppPath = os.path.join(AppDir, AppName+'.app')
             if os.path.exists(AppPath) :
                 OKAY=True

    if DEBUG :
        if not OKAY :
            print('    ' + Program + ' not found')
        else:
            print('    ' + Program + ' found')

    return OKAY

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
class Parameters:
    """
      	Wrapper class for command line parameters
      	"""
    def __init__(self):
        """
     	  Initializes arguments:
                PLATFORM = ""
                INSTALL = False
                UPDATE = False
                SETVAR = False
                VSTRING = ""
                VNAME   = ""
                VALUE   = ""
                BIRCH = ""
                PFN= ""


     	  Then calls read_args() to fill in their values from command line
          """
        self.INSTALL = False
        self.UPDATE  = False
        self.SETVAR  = False
        self.VSTRING = ""
        self.VNAME   = ""
        self.VALUE   = ""
        self.PLATFORM = ""                
        self.BIRCH = "" 
        self.read_args()
        self.PFN = os.path.join(self.BIRCH , 'local' , 'admin' , 'BLproperties.'+ self.PLATFORM)


        if DEBUG :
            print('------------ Parameters from command line ------')
            print('    BIRCH: ' + self.BIRCH)
            print('    PLATFORM: ' + self.PLATFORM)
            print('    INSTALL: ' + str(self.INSTALL))
            print('    UPDATE: ' + str(self.UPDATE))
            print('    SETVAR: ' + str(self.SETVAR))
            print('    VNAME: ' + self.VNAME)
            print('    VALUE: ' + self.VALUE) 
            print('    Property file: ' + self.PFN)
            print()  

    def read_args(self):
        """
        	Read command line arguments into a Parameter object
    	"""
            
        parser = OptionParser()
        parser.add_option("--platform", dest="platform", action="store", default="linux_x86_64",
                          help="specify the os/hardware platform")
        parser.add_option("--birchdir", dest="birch", action="store", default="",
                          help="path to BIRCH installation directory")
        parser.add_option("--install", dest="install", action="store_true", default=False,
                          help="in a new install, set environment variables to default values")
        parser.add_option("--update", dest="update", action="store_true", default=False,
                          help="in an update, set environment variables to local values, or set to default values")
        parser.add_option("--setvar", dest="vstring", action="store", default="",
                          help="set an environment variable to the specified value")

        (options, args) = parser.parse_args() 
        self.PLATFORM = options.platform
        self.BIRCH = options.birch
        self.INSTALL = options.install 
        self.UPDATE = options.update
        self.VSTRING = options.vstring
        if self.VSTRING != "" : 
            self.SETVAR = True
            tokens = self.VSTRING.split("=")
            self.VNAME = tokens[0]
            self.VALUE = tokens[1]
             
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
class BLProperties:
    """
      	Data and methods for the BL properties files.
      	"""
    def __init__(self,P):
        """
     	  Initializes arguments:
                dict = {}
          """
        self.dict = {}
        for var in BLvariables:
            self.dict[var] = ""        
        self.ReadBLProperties(P.PFN)
        if DEBUG :
            print('- - - - - BL properties - - - - -')
            for k in self.dict :
                print('    ' + k + ',' + self.dict[k])

    def ReadBLProperties(self,PFN):
        """
        	Read current values of BLvariables from BL.properties.<platform> file.
    	"""

        if os.path.exists(PFN) :
            Pfile = open(PFN,'r')
            for line in Pfile :
                line = line.strip()
                # ignore blank lines and comment lines
                if (line != "" and line[0] != '#') :
                    print(line)
                    tokens = line.split("=")
                    if tokens[0] in BLvariables :
                        self.dict[tokens[0]] = tokens[1]
            Pfile.close()                


    def WriteBLProperties(self,PFN):
        """
        	Write current values of BLvariables to BL.properties.<platform> file.
    	"""

        Pfile = open(PFN,'w')
        Pfile.write('# DO NOT EDIT THIS FILE!\n')
        Pfile.write('# This file is automatically generated by BLHelper.py during installation,\n')
        Pfile.write('# update or by birchadmin --> BLHelper\n')
        for k in self.dict :
            Pfile.write(k + '=' + self.dict[k] + '\n')            
        Pfile.close()

    def WriteSetblenvBourne(self,P):
        """
        	Write bash code for setting BLvariables to setblenv.profile.<platform>.source file.
		Used for Bourne type shells eg. bash, sh
    	"""

        ENVFN = os.path.join(P.BIRCH, 'admin', 'setblenv.profile.' + P.PLATFORM + '.source')
        Pfile = open(ENVFN,'w')
        Pfile.write('# DO NOT EDIT THIS FILE!\n')
        Pfile.write('# This file is automatically generated by BLHelper.py during installation,\n')
        Pfile.write('# update or by birchadmin --> BLHelper\n')
        #Enclose value of argument in single quotes. This is maninly for cases such as
        #BL_Terminal='gnome-terminal -e'
        for k in self.dict :
            Pfile.write(k + "='" + self.dict[k] + "'\n") 
        Pfile.write('export ')
        for var in BLvariables :
            Pfile.write(' ' + var) 
        Pfile.write('\n')              
        Pfile.close()
        chmod_ar(ENVFN)

    def WriteSetblenvCsh(self,P):
        """
        	Write csh code for setting BLvariables to setblenv.csh.<platform>.source file.
		Used for C type shells eg. csh, tcsh
    	"""

        ENVFN = os.path.join(P.BIRCH, 'admin', 'setblenv.cshrc.' + P.PLATFORM + '.source')
        Pfile = open(ENVFN,'w')
        Pfile.write('# DO NOT EDIT THIS FILE!\n')
        Pfile.write('# This file is automatically generated by BLHelper.py during installation,\n')
        Pfile.write('# update or by birchadmin --> BLHelper\n')
        for k in self.dict :
            Pfile.write('setenv ' +k + ' ' + self.dict[k] + '\n')             
        Pfile.close()
        chmod_ar(ENVFN)

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
class BLChoices:
    """
      	Data and methods for BLHelper.blmenu file.
      	"""
    def __init__(self,P):
        """
     	  Initializes arguments:
                dict = {}
          """
        self.dict = {}
        self.ReadBLChoices(P)
        if DEBUG :
            print('- - - - - BL Choices - - - - -')
            for var in BLvariables:
                print('    ' + var)
                print('    ' + str(self.dict[var]))


    def ReadBLChoices(self,P):
        """
        	Read choice tuples from BL_variable_<platform>.list file.
                Choices are checked to see if the binary file for the program exists.
                Choices are only added to the list if the binary is found.
    	"""
        for var in BLvariables:
            self.dict[var] = []
            VFN = os.path.join(P.BIRCH, 'admin', 'BLHelper', var + '_' + P.PLATFORM + '.list')
            print(VFN) 
            if os.path.exists(VFN) :
                Vfile = open(VFN,'r')
                for line in Vfile :
                    line = line.strip()
                    tokens = line.split(',')
                    if len(tokens) == 2 :
                        if BinaryExists(tokens[1],P.PLATFORM) :
                            self.dict[var].append([tokens[0],tokens[1]])                        
                Vfile.close()           

    def WriteBLmenu(self,P,Props):
        """
        	Create a BioLegato .blmenu file with choices for each helper application.
                This function reads a template .blmenu filefunction and replaces tags of the form
                
                <REPLACE name=BLvariable>
                
                with a tuple list for a combobox. Output is written to HelperApps.blmenu. 
    	"""

        def GetValue(line) :
            """
            Read value from label/value pair of the form label=value
            """
            #Get rid of newline and terminal '>' character
            line=line.strip()[:-1]
            tokens =line.split("=")
            return tokens[1]

        def GetDefaultNum(var,Props) :
            """
            If var is in Choices, return the index of the choice.
            Otherwise, return the value for a Custom command,
            which will always be the last choice in the list 
            """
            print('var: ' + var)
            defaultnum = 0
            l = len(self.dict[var])
            FOUND = False
            while (defaultnum < l) and (not FOUND) :
                if self.dict[var][defaultnum][1] == Props.dict[var] :
                    FOUND = True
                else :
                    defaultnum += 1
            return defaultnum


        Directory = os.path.join(P.BIRCH, 'dat', 'birchadmin', 'PCD', 'Edit')
        TemplateFN = os.path.join(Directory, 'HelperApps.blmenu.template')
        OutputFN = os.path.join(Directory, 'HelperApps.blmenu')
        Templatefile = open(TemplateFN,'r')
        OutputFile = open(OutputFN,'w')
        OutputFile.write('# DO NOT EDIT THIS FILE!\n')
        OutputFile.write('# This file is automatically generated by BLHelper.py during installation,\n')
        OutputFile.write('# update or by birchadmin --> BLHelper\n')
        INDENT8 = '        '
        INDENT12 = '            '
        defaultnum = 0
        for line in Templatefile :

            if line.startswith("<REPLACE defaultnum=") :
                var = GetValue(line)
                if var in BLvariables :
                    defaultnum = GetDefaultNum(var,Props)
                    OutputFile.write(INDENT8 + 'default     ' + str(defaultnum) + "\n" )  

            elif line.startswith("<REPLACE list=") :
                var = GetValue(line)
                if var in BLvariables :
                    for t in self.dict[var] :
                        label = t[0]
                        value = t[1]
                        OutputFile.write(INDENT12 + '"' + label + '" "' + value + '"' + "\n" )       

            elif line.startswith("<REPLACE defaulttext=") :
                var = GetValue(line)
                if (var in BLvariables and defaultnum > 0) :
                    defaulttext = Props.dict[var]
                else :
                    defaulttext = ""
                OutputFile.write(INDENT8 + 'default    ' + '"' + defaulttext + '"' + "\n" )
                defaultnum = 0    

            else:
                OutputFile.write(line)
                    
        OutputFile.close()
        chmod_arx(OutputFN)

#======================== MAIN PROCEDURE ==========================
def main():
    """
        Called when not in documentation mode.
        """
	
    # Read parameters from command line
    P = Parameters()

    if P.PLATFORM in BLplatforms :

        # Special case: If BLProperties file doesn't exist, we treat it as a fresh install.
        # This should only occur when upgrading from an earlier version of BIRCH pre-dating
        # the BLvariables ie. BIRCH Version 3.0 or earlier.
        if not os.path.exists(P.PFN) :
            P.UPDATE = False
            P.INSTALL = True

        if P.INSTALL:
            print("Install")
            Props = BLProperties(P)
            Choices = BLChoices(P)
            for var in BLvariables :
                # Select the best choice for this platform
                # This will be the first one in the choice list
                Props.dict[var] = Choices.dict[var][0][1]
            Props.WriteBLProperties(P.PFN)
            Props.WriteSetblenvBourne(P)
            Props.WriteSetblenvCsh(P)
            Choices.WriteBLmenu(P,Props)
            
        elif P.UPDATE:
            print("Update")
            Props = BLProperties(P)
            Choices = BLChoices(P)
            for var in BLvariables :
                # The only time we change an existing variable during an update is if
                # we discover that it no longer exists in the path.
                if not BinaryExists(Props.dict[var],P.PLATFORM) :
                    # Select the best choice for this platform
                    # This will be the first one in the choice list
                    Props.dict[var] = Choices.dict[var][0][1]
            Props.WriteBLProperties(P.PFN)
            Props.WriteSetblenvBourne(P)
            Props.WriteSetblenvCsh(P)
            Choices.WriteBLmenu(P,Props)   
                     
        elif P.SETVAR:
            print("Setvar")
            Props = BLProperties(P)
            Choices = BLChoices(P)
            if BinaryExists(P.VALUE,P.PLATFORM) :
                Props.dict[P.VNAME] = P.VALUE
                Props.WriteBLProperties(P.PFN)
                Props.WriteSetblenvBourne(P)
                Props.WriteSetblenvCsh(P)
                Choices.WriteBLmenu(P,Props)
            
        else:
            print(USAGE)          

        BM.exit_success()

    else:
        print('BLHelper.py: Invalid platform ' + P.PLATFORM)

if (BM.documentor() or "-test" in sys.argv):
    pass
else:
    main()
