#!/usr/bin/env python

# -*- coding: UTF-8 -*-

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#                                                                         #
#  Copyright (C) 2015-2018 Simon Stuerz <simon.stuerz@guh.io>             #
#  Copyright (C) 2014-2018 Michael Zanetti <michael.zanetti@guh.io>       #
#                                                                         #
#  This file is part of nymea.                                            #
#                                                                         #
#  nymea 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, version 2 of the License.                #
#                                                                         #
#  nymea 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 nymea. If not, see <http://www.gnu.org/licenses/>.          #
#                                                                         #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

import argparse
import traceback
import json
import os
import subprocess

__version__='1.0.2'

##################################################################################################################
# Methods

#-----------------------------------------------------------------------------------------------------------------
def printInfo(info):
    if args.filetype is 'i':
        print(info)


#-----------------------------------------------------------------------------------------------------------------
def printWarning(warning):
    print('Warning: ' + warning)


#-----------------------------------------------------------------------------------------------------------------
def printError(error):
    print('Error: ' + error)


#-----------------------------------------------------------------------------------------------------------------
def writeToFile(line):
    outputFile.write('%s\n' % line)


#-----------------------------------------------------------------------------------------------------------------
def extractPlugin(pluginMap):
    variableName = 'pluginId'
    if not variableName in variableNames:
        variableNames.append(variableName)
        printInfo('Define PluginId pluginId = %s' % (pluginMap['id']))
        if args.filetype is 'i':
            writeToFile('PluginId pluginId = PluginId(\"%s\");' % (pluginMap['id']))
        addTranslationString(pluginMap['displayName'], 'The name of the plugin %s (%s)' % (pluginMap['name'], pluginMap['id']))
        createExternDefinition('PluginId', variableName)

    # Extract plugin params (configurations)
    if 'paramTypes' in pluginMap:
        extractParamTypes(pluginMap['paramTypes'], pluginMap['name'][0].lower() + pluginMap['name'][1:], "", 'plugin')

    if 'vendors' in pluginMap:
        extractVendors(pluginMap['vendors'])


#-----------------------------------------------------------------------------------------------------------------
def extractParamTypes(paramTypes, deviceClassName, typeClass, typeName):
    for paramType in paramTypes:
        try:
            variableName = '%sParamTypeId' % (deviceClassName + typeName[0].capitalize() + typeName[1:] + typeClass + paramType['name'][0].capitalize() + paramType['name'][1:])
            if not variableName in variableNames:
                variableNames.append(variableName)
                printInfo('Define ParamTypeId %s = %s' % (variableName, paramType['id']))
                if args.filetype is 'i':
                    writeToFile('ParamTypeId %s = ParamTypeId(\"%s\");' % (variableName, paramType['id']))
                addTranslationString(paramType['displayName'], 'The name of the ParamType (DeviceClass: %s, %sType: %s, ID: %s)' % (deviceClassName, typeClass, typeName, paramType['id']))
                createExternDefinition('ParamTypeId', variableName)
            else:
                printWarning('Duplicated variable name \"%s\" for ParamTypeId %s -> skipping' % (variableName, paramType['id']))
        except:
            pass


#-----------------------------------------------------------------------------------------------------------------
def extractVendors(vendors):
    for vendor in vendors:
        try:
            variableName = '%sVendorId' % (vendor['name'])
            if not variableName in variableNames:
                variableNames.append(variableName)
                printInfo('Define VendorId %s = %s' % (variableName, vendor['id']))
                if args.filetype is 'i':
                    writeToFile('VendorId %s = VendorId(\"%s\");' % (variableName, vendor['id']))
                addTranslationString(vendor['displayName'], 'The name of the vendor (%s)' % vendor['id'])
                createExternDefinition('VendorId', variableName)
            else:
                printWarning('Duplicated variable name \"%s\" for VendorId %s -> skipping' % (variableName, param['id']))
        except:
            pass

        if 'deviceClasses' in vendor:
            extractDeviceClasses(vendor['deviceClasses'])


#-----------------------------------------------------------------------------------------------------------------
def extractDeviceClasses(deviceClasses):
    for deviceClass in deviceClasses:
        try:
            variableName = '%sDeviceClassId' % (deviceClass['name'])

            if 'pairingInfo' in deviceClass:
                addTranslationString(deviceClass['pairingInfo'], 'The pairing info of deviceClass %s' % deviceClass['name'])

            if not variableName in variableNames:
                variableNames.append(variableName)
                printInfo('Define DeviceClassId %s = %s' % (variableName, deviceClass['id']))
                if args.filetype is 'i':
                    writeToFile('DeviceClassId %s = DeviceClassId(\"%s\");' % (variableName, deviceClass['id']))
                addTranslationString(deviceClass['displayName'], 'The name of the DeviceClass (%s)' %(deviceClass['id']))
                createExternDefinition('DeviceClassId', variableName)
            else:
                printWarning('Duplicated variable name \"%s\" for DeviceClassId %s -> skipping' % (variableName, deviceClass['deviceClassId']))

        except:
            pass

        if 'paramTypes' in deviceClass:
            extractParamTypes(deviceClass['paramTypes'], deviceClass['name'], "", 'device')

        if 'discoveryParamTypes' in deviceClass:
            extractParamTypes(deviceClass['discoveryParamTypes'], deviceClass['name'], "", 'discovery')

        if 'stateTypes' in deviceClass:
            extractStateTypes(deviceClass['stateTypes'], deviceClass['name'],)

        if 'actionTypes' in deviceClass:
            extractActionTypes(deviceClass['actionTypes'], deviceClass['name'])

        if 'eventTypes' in deviceClass:
            extractEventTypes(deviceClass['eventTypes'], deviceClass['name'])


#-----------------------------------------------------------------------------------------------------------------
def extractStateTypes(stateTypes, deviceClassName):
    for stateType in stateTypes:
        try:
            # Define StateType
            variableName = '%s%sStateTypeId' % (deviceClassName, stateType['name'][0].capitalize() + stateType['name'][1:])
            #addTranslationString(stateType['name'], 'The name of the stateType (%s) of DeviceClass %s' % (stateType['id'], deviceClassName))
            if not variableName in variableNames:
                variableNames.append(variableName)
                printInfo('Define StateTypeId %s = %s' % (variableName, stateType['id']))
                if args.filetype is 'i':
                    writeToFile('StateTypeId %s = StateTypeId(\"%s\");' % (variableName, stateType['id']))
                createExternDefinition('StateTypeId', variableName)
            else:
                printWarning('Duplicated variable name \"%s\" for StateTypeId %s -> skipping' % (variableName, stateType['id']))

            # Create EventTypeId for this state
            variableName = '%s%sEventTypeId' % (deviceClassName, stateType['name'][0].capitalize() + stateType['name'][1:])
            if not variableName in variableNames:
                addTranslationString(stateType['displayNameEvent'], 'The name of the autocreated EventType (DeviceClass: %s, StateType: %s, ID: %s)' % (deviceClassName, stateType['name'], stateType['id']))
                variableNames.append(variableName)
                printInfo('Define EventTypeId %s = %s' % (variableName, stateType['id']))
                if args.filetype is 'i':
                    writeToFile('EventTypeId %s = EventTypeId(\"%s\");' % (variableName, stateType['id']))
                createExternDefinition('EventTypeId', variableName)
            else:
                printWarning('Duplicated variable name \"%s\" for autocreated EventTypeId %s -> skipping' % (variableName, stateType['id']))

            # ParamType for the autocreated EventType
            variableName = '%s%sEvent%sParamTypeId' % (deviceClassName, stateType['name'][0].capitalize() + stateType['name'][1:], stateType['name'][0].capitalize() + stateType['name'][1:])
            if not variableName in variableNames:
                variableNames.append(variableName)
                printInfo('Define ParamTypeId %s for StateType %s = %s' % (variableName, variableName, stateType['id']))
                addTranslationString(stateType['displayName'], 'The name of the ParamType for the autocreated EventType (DeviceClass: %s, StateType: %s, ID: %s' % (deviceClassName, stateType['name'], stateType['id']))
                if args.filetype is 'i':
                    writeToFile('ParamTypeId %s = ParamTypeId(\"%s\");' % (variableName, stateType['id']))
                createExternDefinition('ParamTypeId', variableName)
            else:
                printWarning('Duplicated variable name \"%s\" for ParamTypeId %s -> skipping' % (variableName, stateType['id']))

            # Create ActionTypeId and ParamTypeId for action if the state is writable
            if 'writable' in stateType and stateType['writable']:
                # Create ActionType for the writable state
                variableName = '%s%sActionTypeId' % (deviceClassName, stateType['name'][0].capitalize() + stateType['name'][1:])
                if not variableName in variableNames:
                    variableNames.append(variableName)
                    printInfo('Define ActionTypeId for writable StateType %s = %s' % (variableName, stateType['id']))
                    addTranslationString(stateType['displayNameAction'], 'The name of the autocreated ActionType (DeviceClass: %s, StateType: %s, ID: %s)' % (deviceClassName, stateType['name'], stateType['id']))
                    if args.filetype is 'i':
                        writeToFile('ActionTypeId %s = ActionTypeId(\"%s\");' % (variableName, stateType['id']))
                    createExternDefinition('ActionTypeId', variableName)
                else:
                    printWarning('Duplicated variable name \"%s\" for autocreated ActionTypeId %s -> skipping' % (variableName, stateType['id']))

                # ParamType for the autocreated ActionType
                variableName = '%s%sAction%sParamTypeId' % (deviceClassName, stateType['name'][0].capitalize() + stateType['name'][1:], stateType['name'][0].capitalize() + stateType['name'][1:])
                if not variableName in variableNames:
                    variableNames.append(variableName)
                    printInfo('Define ParamTypeId %s for autocreated ActionType %s = %s' % (variableName, variableName, stateType['id']))
                    addTranslationString(stateType['displayName'], 'The name of the ParamType for the autocreated ActionType (DeviceClass: %s, StateType: %s, ID: %s)' % (deviceClassName, stateType['name'], stateType['id']))
                    if args.filetype is 'i':
                        writeToFile('ParamTypeId %s = ParamTypeId(\"%s\");' % (variableName, stateType['id']))
                    createExternDefinition('ParamTypeId', variableName)
                else:
                    printWarning('Duplicated variable name \"%s\" for ParamTypeId %s -> skipping' % (variableName, stateType['id']))

        except:
            pass


#-----------------------------------------------------------------------------------------------------------------
def extractActionTypes(actionTypes, deviceClassName):
    for actionType in actionTypes:
        try:
            # Define ActionTypeId
            variableName = '%s%sActionTypeId' % (deviceClassName, actionType['name'][0].capitalize() + actionType['name'][1:])
            if not variableName in variableNames:
                variableNames.append(variableName)
                addTranslationString(actionType['displayName'], 'The name of the ActionType %s of deviceClass %s' % (actionType['id'], deviceClassName))
                if args.filetype is 'i':
                    writeToFile('ActionTypeId %s = ActionTypeId(\"%s\");' % (variableName, actionType['id']))
                createExternDefinition('ActionTypeId', variableName)
            else:
                printWarning('Duplicated variable name \"%s\" for ActionTypeId %s -> skipping' % (variableName, actionType['id']))

        except:
            pass

        # Define paramTypes of this ActionType
        if 'paramTypes' in actionType:
            extractParamTypes(actionType['paramTypes'], deviceClassName, 'Action', actionType['name'])


#-----------------------------------------------------------------------------------------------------------------
def extractEventTypes(eventTypes, deviceClassName):
    for eventType in eventTypes:
        try:
            # Define EventTypeId
            variableName = '%s%sEventTypeId' % (deviceClassName, eventType['name'][0].capitalize() + eventType['name'][1:])
            if not variableName in variableNames:
                variableNames.append(variableName)
                addTranslationString(eventType['displayName'], 'The name of the EventType %s of deviceClass %s' % (eventType['id'], deviceClassName))
                if args.filetype is 'i':
                    writeToFile('EventTypeId %s = EventTypeId(\"%s\");' % (variableName, eventType['id']))
                createExternDefinition('EventTypeId', variableName)
            else:
                printWarning('Duplicated variable name \"%s\" for EventTypeId %s -> skipping' % (variableName, eventType['id']))
        except:
            pass

        # Define paramTypes of this EventType
        if 'paramTypes' in eventType:
            extractParamTypes(eventType['paramTypes'], deviceClassName, 'Event', eventType['name'])


#-----------------------------------------------------------------------------------------------------------------
def createExternDefinition(type, name):
    definition = {}
    definition['type'] = type
    definition['variable'] = name
    externDefinitions.append(definition)


#-----------------------------------------------------------------------------------------------------------------
def addTranslationString(string, comment):
    translationStrings.append([string, comment])


#-----------------------------------------------------------------------------------------------------------------
def writeTranslationStrings():
    if len(translationStrings) is not 0:
        writeToFile('// Translation strings')
        writeToFile('const QString translations[] {')

        for index, value in enumerate(translationStrings):
            writeToFile('    //: %s' % value[1])
            if index != len(translationStrings) - 1:
                writeToFile('    QT_TRANSLATE_NOOP(\"%s\", \"%s\"), \n' % (pluginMap['name'], value[0]))
            else:
                writeToFile('    QT_TRANSLATE_NOOP(\"%s\", \"%s\")' % (pluginMap['name'], value[0]))

        writeToFile('};')


#-----------------------------------------------------------------------------------------------------------------
def createTranslationFiles():
    for translation in args.translations:
        translationFile = (sourceDir + '/' + translation)
        path, fileName = os.path.split(translationFile)
        translationOutput = (path + '/' + pluginMap['id'] + '-' + os.path.splitext(fileName)[0] + '.qm')
        printInfo(' --> Translation update %s' % translationFile)
        printInfo(subprocess.check_output(['mkdir', '-p', path]))
        printInfo(subprocess.check_output(['lupdate', '-recursive', '-no-obsolete', sourceDir, (args.builddir + '/' + args.output), '-ts', translationFile]))
        printInfo(' --> Translation release %s' % translationOutput)
        printInfo(subprocess.check_output(['lrelease', translationFile, '-qm', translationOutput]))
        printInfo(' --> Copy translation files to build dir %s' % args.builddir + '/translations/')
        subprocess.check_output(['rsync', '-a', translationOutput, args.builddir + '/translations/'])


#-----------------------------------------------------------------------------------------------------------------
def writePluginInfoFile():
    print(' --> Generate plugininfo.h for plugin \"%s\" = %s' % (pluginMap['name'], pluginMap['id']))

    writeToFile('/* This file is generated by the nymea build system. Any changes to this file will')
    writeToFile(' * be lost.')
    writeToFile(' *')
    writeToFile(' * If you want to change this file, edit the plugin\'s json file.')
    writeToFile(' */')
    writeToFile('')
    writeToFile('#ifndef PLUGININFO_H')
    writeToFile('#define PLUGININFO_H')
    writeToFile('')
    writeToFile('#include <QLoggingCategory>')
    writeToFile('#include <QObject>')
    writeToFile('')
    writeToFile('#include \"typeutils.h\"')
    writeToFile('')
    writeToFile('// Id definitions')
    extractPlugin(pluginMap)
    writeToFile('')
    writeToFile('// Logging category')

    if 'name' in pluginMap:
        debugCategoryName = pluginMap['name'][0].capitalize() + pluginMap['name'][1:]
        writeToFile('Q_DECLARE_LOGGING_CATEGORY(dc%s)' % debugCategoryName)
        writeToFile('Q_LOGGING_CATEGORY(dc%s, \"%s\")' % (debugCategoryName, debugCategoryName))
        printInfo('Define logging category: \'dc%s\'' % debugCategoryName)

    writeToFile('')

    # Write translation strings
    writeTranslationStrings()

    writeToFile('')
    writeToFile('#endif // PLUGININFO_H')
    outputFile.close()
    print(' --> Generated successfully \"%s\"' % (args.output))


#-----------------------------------------------------------------------------------------------------------------
def writeExternPluginInfoFile():
    print(' --> Generate extern-plugininfo.h for plugin \"%s\" = %s' % (pluginMap['name'], pluginMap['id']))
    extractPlugin(pluginMap)
    writeToFile('/* This file is generated by the nymea build system. Any changes to this file will')
    writeToFile(' * be lost.')
    writeToFile(' *')
    writeToFile(' * If you want to change this file, edit the plugin\'s json file.')
    writeToFile(' */')
    writeToFile('')
    writeToFile('#ifndef EXTERNPLUGININFO_H')
    writeToFile('#define EXTERNPLUGININFO_H')
    writeToFile('#include \"typeutils.h\"')
    writeToFile('#include <QLoggingCategory>')
    writeToFile('')
    writeToFile('// Id definitions')

    for externDefinition in externDefinitions:
        writeToFile('extern %s %s;' % (externDefinition['type'], externDefinition['variable']))

    writeToFile('')
    writeToFile('// Logging category definition')

    if 'name' in pluginMap:
        debugCategoryName = pluginMap['name'][0].capitalize() + pluginMap['name'][1:]
        writeToFile('Q_DECLARE_LOGGING_CATEGORY(dc%s)' % debugCategoryName)

    writeToFile('')
    writeToFile('#endif // EXTERNPLUGININFO_H')
    outputFile.close()
    print(' --> Generated successfully \'%s\'' % (args.output))


##################################################################################################################
# Main
##################################################################################################################

if __name__ == '__main__':
    # Argument parser
    parser = argparse.ArgumentParser(description='The nymea-generateplugininfo is a precompiler for building plugins. This precompiler will create a plugininfo.h containing the uuid definitions from the plugin json file and creates the translations for the plugin.')
    parser.add_argument('-j', '--jsonfile', help='The JSON input file name with the plugin description', metavar='jsonfile', required=True)
    parser.add_argument('-b', '--builddir', help='The path to the build directory of the plugin where the plugininfo.h file can be found.', metavar='buildpath', required=True)
    parser.add_argument('-f', '--filetype', help='The file type to generate: e = extern infofile, i = infofile', action='store', choices=['e', 'i'], default='i')
    parser.add_argument('-o', '--output', help='The plugininfo.h outputFile with the uuid declarations', metavar='output')
    parser.add_argument('-t', '--translations', help='The translation files for the plugin.', nargs='*', type=str, metavar='*.ts')
    parser.add_argument('-v', '--version', action='version', version=__version__)
    args = parser.parse_args()

    # Get the source directors
    sourceDir = os.path.dirname(os.path.abspath(args.jsonfile))

    # Print build information for debugging
    printInfo('Json file: %s' % args.jsonfile)
    printInfo('Output: %s/%s' % (args.builddir, args.output))
    printInfo('Build directory: %s' % args.builddir)
    printInfo('Source directory: %s' % sourceDir)
    printInfo('Translations: %s' % args.translations)
    printInfo('FileType: %s' % args.filetype)

    # Tuple ('string to translate', 'comment for translater')
    translationStrings = []

    variableNames = []
    externDefinitions = []

    # Open files
    try:
        inputFile = open(args.jsonfile, 'r')
    except:
        printError('Could not open file \"%s\"' % (args.jsonfile))
        exit -1

    try:
        outputFile = open(args.builddir + '/' + args.output, 'w')
    except:
        printError('Could not open file \"%s\"' % (args.jsonfile))
        exit -1

    # Read json file
    try:
        pluginMap = json.loads(inputFile.read())
        inputFile.close()
    except ValueError as error:
        printError(' --> Could not load json input file \"%s\"' % (args.input))
        printError('     %s' % (error))
        inputFile.close()
        exit -1

    # If there is no translation yet, generate an empty one
    translationsDir = "%s/translations/" % sourceDir
    baseTranslationFile = "%s/%s-en_US.ts" % (translationsDir, pluginMap['id'])
    if not os.path.isfile(baseTranslationFile):
        try:
            os.stat(translationsDir)
        except:
            os.mkdir(translationsDir)
        try:
            tsFile = open(baseTranslationFile, 'w')
            tsFile.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\">\n</TS>")
            tsFile.close()
            printInfo("Successfully generated base translation file: %s" % baseTranslationFile)
        except:
            printError("Could not generate %s" % baseTranslationsFile)


    # Write files
    if args.filetype is 'i':
        writePluginInfoFile()
    else:
        writeExternPluginInfoFile()







