#!/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 iefiles

from iecommon import *
from model.Register import DesignRegister
from model.Block import DesignBlock
from model.Class import DesignClass
import libxml2

def findBlockServerSetActionName(fesaRoot, propName):
    properties = fesaRoot.xpathEval("/equipment-model/interface/device-interface/*/*[@name='" + propName + "']")
    for property in properties:
        return property.xpathEval("set-action/server-action-ref")[0].prop("server-action-name-ref")
    raise Exception("Error: Server Action for Property '" + propName + "' 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):
    designClass = DesignClass.getDesignClassFromRootNode(silecsRoot)
    source =  fesaTemplates.genHTop(className)
    
    for block in designClass.getDesignBlocks():
        if block.isWritable():
            serverActionName = findBlockServerSetActionName(fesaRoot,block.getFesaName())
            source += fesaTemplates.genHTopBlock(className, serverActionName)
        
    source += fesaTemplates.genHTop2(className)
        
    for block in designClass.getDesignBlocks():
        if block.isAcquisition():
            source += fesaTemplates.genHBlock('RO', block.name,block.getFesaName() )
        elif block.isCommand():
            source += fesaTemplates.genHBlock('WO', block.name,block.getFesaName())
        else:   # Setting
            source += fesaTemplates.genHBlock('RW', block.name,block.getFesaName())
    
    source += fesaTemplates.genHBottom(className)

    for block in designClass.getDesignBlocks():
        source += fesaTemplates.genHDeclBlocks(block.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):
    designClass = DesignClass.getDesignClassFromRootNode(silecsRoot)
    finalSource =  fesaTemplates.genCTop(className)
    blockList = designClass.getDesignBlocks()
    for block in blockList:
        finalSource +=  fesaTemplates.genCGlobal(className, block.name)
    
    finalSource += fesaTemplates.genCPart1(className)
    
    for block in blockList:
        finalSource += fesaTemplates.genCBlockConstr(block.name, className)
        
    finalSource += fesaTemplates.genCPart2(className)
    
    for block in blockList:
        if (block.isWritable()):
            # WARNING: In order to have multiplexed FESA fields, the corresponding SILECS registers' synchro mode must be 'NONE' 
            finalSource += fesaTemplates.genCSetPLC(className, block.name)
        
    finalSource += fesaTemplates.genCPart3(className)
    
    for block in blockList:
        if (block.isReadable()):
            # WARNING: In order to have multiplexed FESA fields, the corresponding SILECS registers' synchro mode must be 'NONE'
            finalSource += fesaTemplates.genCGetPLC(className, block.name)
    
    finalSource += fesaTemplates.genCPart4(className)
    
    for block in blockList:
        if block.isReadable():
            finalSource += fesaTemplates.genCCommonGet(block.name,className)
            finalSource += '\n'
            finalSource += fesaTemplates.cRecv
            for register in  block.getDesignRegisters():
                if register.valueType == 'string':
                    finalSource += fesaTemplates.genCGetStringReg(register)
                elif register.valueType == 'stringArray':
                    finalSource += fesaTemplates.genCGetStringArrayReg(register)
                elif register.valueType == 'stringArray2D':
                    iecommon.logError('ERROR: In register '+register.name+' - 2D array of strings not supported in FESA.', True, {'errorlog': True})
                elif register.valueType == 'scalar':
                    finalSource += fesaTemplates.genCGetScalarReg(register)
                elif register.valueType == 'array':
                    finalSource += fesaTemplates.genCGetArrayReg(register)
                elif register.valueType == 'array2D':
                    finalSource += fesaTemplates.genCGetArray2DReg(register)
            finalSource += '\n    }\n'

        if block.isWritable():
            finalSource += fesaTemplates.genCCommonSet(block.name,className)
            for register in  block.getDesignRegisters():
                if register.valueType == 'string':
                    finalSource += fesaTemplates.genCSetStringReg(register)
                elif register.valueType == 'stringArray':
                    finalSource += fesaTemplates.genCSetStringArrayReg(register)
                elif register.valueType == 'stringArray2D':
                    iecommon.logError('ERROR: In register '+register.name+' - 2D array of strings not supported in FESA.', True, {'errorlog': True})
                elif register.valueType == 'scalar':
                    finalSource += fesaTemplates.genCSetScalarReg(register)
                elif register.valueType == 'array':
                    finalSource += fesaTemplates.genCSetArrayReg(register)
                elif register.valueType == 'array2D':
                    finalSource += fesaTemplates.genCSetArray2DReg(register)
            finalSource += fesaTemplates.cSend
            finalSource += '    }\n'
            finalSource += fesaTemplates.genCDatatypeSet(block.name,block.getFesaName(), className)
            
            for register in block.getDesignRegisters():
                if register.valueType == 'string':
                    finalSource += fesaTemplates.genCSetStringRegData(register)
                elif register.valueType == 'stringArray':
                    finalSource += fesaTemplates.genCSetStringArrayRegData(register)
                elif register.valueType == 'stringArray2D':
                    iecommon.logError('ERROR: In register '+register.name+' - 2D array of strings not supported in FESA.', True, {'errorlog': True})
                elif register.valueType == 'array2D':
                    finalSource += fesaTemplates.genCSetArray2DRegData(register)
                elif register.valueType == 'array':
                    finalSource += fesaTemplates.genCSetArrayRegData(register)
                elif register.valueType == 'scalar':
                    finalSource += fesaTemplates.genCSetScalarRegData(register)

            finalSource += fesaTemplates.cSend
            finalSource += '\n    }\n'   # closing bracket for block

    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}):

    generatedCodeFolder = workspacePath + '/' + className + '/generated-silecs/cpp/' + className + '/GeneratedCode'
    generatedCodeFolderNorm = os.path.normpath(generatedCodeFolder)

    iecommon.logInfo("generated code folder:" + generatedCodeFolderNorm,logTopics)

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

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

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