#!/usr/bin/python
# Copyright 2016 CERN and GSI
#
# 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 3 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/>.

import os
import sys
import time
import zlib
import glob
import re
import datetime
import socket

import iecommon
import fesaTemplates
import fesaGeneral
import iefiles

from iecommon import *
from fesaGeneral import *
import libxml2

def findBlockServerSetActionName(fesaRoot, blockName):
    properties = fesaRoot.xpathEval("/equipment-model/interface/device-interface/*/*[@name='" + blockName + "']")
    for property in properties:
        return property.xpathEval("set-action/server-action-ref")[0].prop("server-action-name-ref")
    raise Exception("Error: Server Action for Block '" + blockName + "' not found")

#-------------------------------------------------------------------------  
# Generates the H source file containing general methods
# to synchronise the FESA fields and related PLC registers
# of the FESA server
#-------------------------------------------------------------------------  
def genHSource(className, silecsRoot, fesaRoot, sourcePath,logTopics):
    source =  fesaTemplates.genHTop(className)
    
    for block in silecsRoot.xpathEval('//Block'):
        if block.prop('mode') == 'WRITE-ONLY' or block.prop('mode') == 'READ-WRITE':
            serverActionName = findBlockServerSetActionName(fesaRoot,getFesaName(block))
            source += fesaTemplates.genHTopBlock(className, serverActionName)
        
    source += fesaTemplates.genHTop2(className)
        
    for block in silecsRoot.xpathEval('//Block'):
        if block.prop('mode') == 'READ-ONLY':
            source += fesaTemplates.genHBlock('RO', block.prop('name'),getFesaName(block) )
        elif block.prop('mode') == 'WRITE-ONLY':
            source += fesaTemplates.genHBlock('WO', block.prop('name'),getFesaName(block))
        else:   # READ-WRITE
            source += fesaTemplates.genHBlock('RW', block.prop('name'),getFesaName(block))
    
    source += fesaTemplates.genHBottom(className)

    for block in silecsRoot.xpathEval('//Block'):
        source += fesaTemplates.genHDeclBlocks(block.prop('name'))
    
    source += fesaTemplates.genHClosing(className)
    
    # Create output directory if necessary
    if not os.path.exists(sourcePath):
        os.makedirs(sourcePath)
    iecommon.logDebug("Create directory %s" %sourcePath, logTopics)
    
    # Write to file and save
    sourceFile = sourcePath + "/"+ className + ".h"
    iecommon.logInfo("Generate header file: " + sourceFile, logTopics)
    fdesc = open(sourceFile, "w")
    fdesc.write(source)
    fdesc.close()  
 
    iecommon.logInfo('Header file for '+className+' generated successfully', logTopics)
    
#-------------------------------------------------------------------------  
# Generates the C++ source file containing general 
# methods to synchronise the FESA fields and related PLC 
# registers of the FESA server
#-------------------------------------------------------------------------    
def genCppSource(className, silecsRoot, fesaRoot, sourcePath,logTopics):
    finalSource =  fesaTemplates.genCTop(className)
    blockList = silecsRoot.xpathEval('//Block')
    for block in blockList:
        finalSource +=  fesaTemplates.genCGlobal(className, block.prop('name'))
    
    finalSource += fesaTemplates.genCPart1(className)
    
    for block in blockList:
        finalSource += fesaTemplates.genCBlockConstr(block.prop('name'), className)
        
    finalSource += fesaTemplates.genCPart2(className)
    
    for block in blockList:
        regList = block.xpathEval('Register')
        if (block.prop('mode') == 'WRITE-ONLY' or block.prop('mode') == 'READ-WRITE') and regList[0].prop('synchro') == 'SLAVE':
            # just set the fields if block is WO or RW and register synchronisation is SLAVE
            # WARNING: In order to have multiplexed FESA fields, the corresponding SILECS registers' synchro mode must be 'NONE' 
            finalSource += fesaTemplates.genCSetPLC(className, block.prop('name'))
        
    finalSource += fesaTemplates.genCPart3(className)
    
    for block in blockList:
        regList = block.xpathEval('Register')
        if (block.prop('mode') == 'READ-ONLY' or block.prop('mode') == 'READ-WRITE') and regList[0].prop('synchro') == 'MASTER':
            # just get the fields if block is RO or RW and register synchronisation is MASTER
            # WARNING: In order to have multiplexed FESA fields, the corresponding SILECS registers' synchro mode must be 'NONE'
            finalSource += fesaTemplates.genCGetPLC(className, block.prop('name'))
    
    finalSource += fesaTemplates.genCPart4(className)
    
    for block in blockList:
        
        source = ''     #compute one source at a time
        flagReg = False
        flagDim1 = False
        flagDim2 = False
        
        if block.prop('mode') != 'WRITE-ONLY':
            finalSource += fesaTemplates.genCCommonGet(block.prop('name'),className)

            regList = block.xpathEval('Register')
            source += fesaTemplates.cRecv
            
            for reg in regList:
                type = reg.prop('format')
                if type == 'string':    # string register
                    flagReg = True                    
                    if (reg.prop('array-dim1')) and int(reg.prop('array-dim1')) > 1:   # string array
                        flagDim1 = True                    
                        source += fesaTemplates.genCGetStringArrayReg(reg.prop('name'), getFesaName(reg))
                    else:   # simple string
                        source += fesaTemplates.genCGetStringReg(reg.prop('name'), getFesaName(reg))

                else:   # not string register
                    # uppercasing of type
                    fesaType = iecommon.getFesaDataType(type)
                    type = iecommon.getSilecsDataType(type)
                    if type[0] == 'u':
                        type = type[:2].upper() + type[2:]  # first two characters if unsigned
                        
                        if reg.prop('array-dim2') and int(reg.prop('array-dim2')) > 1:    # 2D array
                            flagReg = True                    
                            flagDim2 = True                    
                            flagDim1 = True                    
                            source += fesaTemplates.genCGetUnsignedArray2DReg(reg.prop('name'), getFesaName(reg), fesaType, type)
                        elif (reg.prop('array-dim1')) and int(reg.prop('array-dim1')) > 1:   # array
                            flagReg = True                    
                            flagDim1 = True                    
                            source += fesaTemplates.genCGetUnsignedArrayReg(reg.prop('name'), getFesaName(reg), fesaType, type)
                        else:    # scalar
                            source += fesaTemplates.genCGetScalarReg(reg.prop('name'), getFesaName(reg), type) 
                            
                    elif type[0] == 'd':    # date type
                        if reg.prop('array-dim2') and int(reg.prop('array-dim2')) > 1:
                            flagReg = True                    
                            flagDim2 = True                    
                            flagDim1 = True                    
                            source += fesaTemplates.genCGetArray2DReg(reg.prop('name'), getFesaName(reg), 'Date')
                        elif (reg.prop('array-dim1')) and int(reg.prop('array-dim1')) > 1:   # array
                            flagReg = True                    
                            flagDim1 = True                    
                            source += fesaTemplates.genCGetArrayReg(reg.prop('name'), getFesaName(reg), 'Date')
                        else:    # scalar
                            source += fesaTemplates.genCGetScalarReg(reg.prop('name'), getFesaName(reg), 'Date')
                        
                    else:
                        type = type[:1].upper() + type[1:]  # only first character if not unsigned
                        if reg.prop('array-dim2') and int(reg.prop('array-dim2')) > 1:    # 2D array
                            flagReg = True                    
                            flagDim2 = True                    
                            flagDim1 = True                    
                            source += fesaTemplates.genCGetArray2DReg(reg.prop('name'), getFesaName(reg), type)
                        elif (reg.prop('array-dim1')) and int(reg.prop('array-dim1')) > 1:   # array
                            flagReg = True                    
                            flagDim1 = True                    
                            source += fesaTemplates.genCGetArrayReg(reg.prop('name'), getFesaName(reg), type)
                        else:    # scalar
                            source += fesaTemplates.genCGetScalarReg(reg.prop('name'), getFesaName(reg), type)
            
            source += '\n\t}'   # closing bracket for block

        #Add data declatation on top if needed and append the final source
        if flagReg == True:
            finalSource += fesaTemplates.cRegVar
        if flagDim1 == True:
            finalSource += fesaTemplates.cGetArrayVar
        if flagDim2 == True:
            finalSource += fesaTemplates.cGetArray2DVar
            
        finalSource += '\n' + source    
                                   
        source = ''     #compute one source at a time
        flagReg = False
        flagDim1 = False
        flagDim2 = False

        if block.prop('mode') != 'READ-ONLY':
            finalSource += fesaTemplates.genCCommonSet(block.prop('name'),className)
            
            regList = block.xpathEval('Register')
            
            for reg in regList:
                type = reg.prop('format')
                if type == 'string':
                    flagReg = True                    
                    if (reg.prop('array-dim1')) and int(reg.prop('array-dim1')) > 1:   # string array
                        flagDim1 = True                    
                        source += fesaTemplates.genCSetStringArrayReg(reg.prop('name'), getFesaName(reg))
                    else:   # simple string
                        source += fesaTemplates.genCSetStringReg(reg.prop('name'), getFesaName(reg))
                
                else:   # not string register              
                    # uppercasing of type
                    type = iecommon.getSilecsDataType(type)
                    lowerType = type+'_t'   # store lowercase type for unsigned registers (fesa type)
                    if type[0] == 'u':  # unsigned type
                        type = type[:2].upper() + type[2:]  # first two characters if unsigned

                        if reg.prop('array-dim2') and int(reg.prop('array-dim2')) > 1:    # 2D array
                            flagReg = True                    
                            flagDim2 = True                    
                            flagDim1 = True                    
                            source += fesaTemplates.genCSetUnsignedArray2DReg(reg.prop('name'), getFesaName(reg), type)
                        elif (reg.prop('array-dim1')) and int(reg.prop('array-dim1')) > 1:   # array
                            flagReg = True                    
                            flagDim1 = True                    
                            source += fesaTemplates.genCSetUnsignedArrayReg(reg.prop('name'), getFesaName(reg), type)
                        else:    # scalar
                            source += fesaTemplates.genCSetScalarUReg(reg.prop('name'), getFesaName(reg), type, lowerType)
    
                    else:   # signed type
                        type = type[:1].upper() + type[1:]  # only first character if not unsigned
                        if reg.prop('array-dim2') and int(reg.prop('array-dim2')) > 1:    # 2D array
                            flagReg = True                    
                            flagDim2 = True                    
                            flagDim1 = True                    
                            source += fesaTemplates.genCSetArray2DReg(reg.prop('name'), getFesaName(reg), type)
                        elif (reg.prop('array-dim1')) and int(reg.prop('array-dim1')) > 1:   # array
                            flagReg = True                    
                            flagDim1 = True                    
                            source += fesaTemplates.genCSetArrayReg(reg.prop('name'), getFesaName(reg), type)
                        else:    # scalar
                            source += fesaTemplates.genCSetScalarReg(reg.prop('name'), getFesaName(reg), type)

            source += fesaTemplates.cSend
            source += '\n\t}'   # closing bracket for block
        
            #Add data declatation on top if needed and append the final source
            if flagReg == True:
                finalSource += fesaTemplates.cRegVar
            if flagDim1 == True:
                finalSource += fesaTemplates.cSetArrayVar
            if flagDim2 == True:
                finalSource += fesaTemplates.cSetArray2DVar
            
            finalSource += '\n' + source    
                      
            source = ''     #compute one source at a time
            flagReg = False
            flagDim1 = False
            flagDim2 = False
    
            finalSource += fesaTemplates.genCDatatypeSet(block.prop('name'),getFesaName(block), className)
            
            regList = block.xpathEval('Register')
            
            for reg in regList:
                type = reg.prop('format')
                if type == 'string':
                    flagReg = True                    
                    if (reg.prop('array-dim1')) and int(reg.prop('array-dim1')) > 1:   # string array
                        flagDim1 = True                    
                        source += fesaTemplates.genCSetStringArrayRegData(reg.prop('name'), getFesaName(reg))
                    else:   # simple string
                        source += fesaTemplates.genCSetStringRegData(reg.prop('name'), getFesaName(reg))
                
                else:   # not string register
                    # uppercasing of type
                    fesaType = iecommon.getFesaDataType(type)
                    type = iecommon.getSilecsDataType(type)
                    lowerType = type+'_t'   # store lowercase type for unsigned registers (fesa type)
                    if type[0] == 'u':  # unsigned type
                        type = type[:2].upper() + type[2:]  # first two characters if unsigned
                        if reg.prop('array-dim2') and int(reg.prop('array-dim2')) > 1:    # 2D array
                            flagReg = True                    
                            flagDim2 = True                    
                            flagDim1 = True                    
                            source += fesaTemplates.genCSetUnsignedArray2DRegData(reg.prop('name'), getFesaName(reg), fesaType, type)
                        elif (reg.prop('array-dim1')) and int(reg.prop('array-dim1')) > 1:   # array
                            flagReg = True                    
                            flagDim1 = True                    
                            source += fesaTemplates.genCSetUnsignedArrayRegData(reg.prop('name'), getFesaName(reg), type)
                        else:    # scalar
                            source += fesaTemplates.genCSetScalarURegData(reg.prop('name'), getFesaName(reg), type, lowerType)
                            
                    else:   # signed type
                        type = type[:1].upper() + type[1:]  # only first character if not unsigned
                        if reg.prop('array-dim2') and int(reg.prop('array-dim2')) > 1:    # 2D array
                            flagReg = True                    
                            flagDim2 = True                    
                            flagDim1 = True                    
                            source += fesaTemplates.genCSetArray2DRegData(reg.prop('name'), getFesaName(reg), fesaType, type)
                        elif (reg.prop('array-dim1')) and int(reg.prop('array-dim1')) > 1:   # array
                            flagReg = True                    
                            flagDim1 = True                    
                            source += fesaTemplates.genCSetArrayRegData(reg.prop('name'), getFesaName(reg), type)
                        else:    # scalar
                            source += fesaTemplates.genCSetScalarRegData(reg.prop('name'), getFesaName(reg), type)
             
            source += fesaTemplates.cSend
            source += '\n\t}'   # closing bracket for block

            #Add data declatation on top if needed and append the final source
            if flagReg == True:
                finalSource += fesaTemplates.cRegVar
            if flagDim1 == True:
                finalSource += fesaTemplates.cSetArrayVar
            if flagDim2 == True:
                finalSource += fesaTemplates.cSetArray2DVar
            
            finalSource += '\n' + source    

    finalSource += '\n}\n'   # closing bracket for class

    # Write to file and save
    sourceFile = sourcePath + "/" + className + ".cpp"
    iecommon.logInfo("Generate source file: " + sourceFile, logTopics)
    fdesc = open(sourceFile, "w")
    fdesc.write(finalSource)
    fdesc.close()

    iecommon.logInfo('Source file for '+className+' generated successfully', logTopics)

def genCppFiles(className, workspacePath, silecsDesignFilePath,logTopics={'errorlog': True}):
    fesaCommonDirectory = iefiles.getFesa3CommonDirectory(workspacePath,className)
    iecommon.logInfo("fesaCommonDirectory:" + fesaCommonDirectory,logTopics)

    if not os.path.exists(fesaCommonDirectory ):
        os.makedirs(fesaCommonDirectory)

    silecsDesignFilePath = iefiles.getSilecsDesignFilePath(workspacePath,className)
    fesaDesignFilePath = workspacePath + "/" + className + "/src/" + className + ".design"
    silecsRoot = libxml2.parseFile(silecsDesignFilePath)
    fesaRoot = libxml2.parseFile(fesaDesignFilePath)

    genHSource(className, silecsRoot, fesaRoot, fesaCommonDirectory,logTopics)
    genCppSource(className, silecsRoot, fesaRoot, fesaCommonDirectory,logTopics)