/**
 ******************************************************************************
 *
 * @file       uavobjectgeneratorgcs.cpp
 * @author     The LibrePilot Project, http://www.librepilot.org Copyright (C) 2016.
 *             The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
 * @brief      produce gcs code for uavobjects
 *
 * @see        The GNU Public License (GPL) Version 3
 *
 *****************************************************************************/
/*
 * 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, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "uavobjectgeneratorgcs.h"

#define VERBOSE             false
#define DEPRECATED          true

#define DEFAULT_ENUM_PREFIX "E_"

using namespace std;

void error(QString msg)
{
    cerr << "error: " << msg.toStdString() << endl;
}

void warning(ObjectInfo *object, QString msg)
{
    cerr << "warning: " << object->filename.toStdString() << ": " << msg.toStdString() << endl;
}

void info(ObjectInfo *object, QString msg)
{
    if (VERBOSE) {
        cout << "info: " << object->filename.toStdString() << ": " << msg.toStdString() << endl;
    }
}

struct Context {
    ObjectInfo *object;
    // enums
    QString    enums;
    QString    enumsCount;
    QString    registerImpl;
    // interface
    QString    fields;
    QString    fieldsInfo;
    QString    properties;
    QString    deprecatedProperties;
    QString    getters;
    QString    setters;
    QString    notifications;
    // implementation
    QString    fieldsInit;
    QString    fieldsDefault;
    QString    propertiesImpl;
    QString    notificationsImpl;
};

struct FieldContext {
    FieldInfo *field;
    // field
    QString   fieldName;
    QString   fieldType;
    // property
    QString   propName;
    QString   ucPropName;
    QString   propType;
    QString   propRefType;
    // deprecation
    bool hasDeprecatedProperty;
    bool hasDeprecatedGetter;
    bool hasDeprecatedSetter;
    bool hasDeprecatedNotification;
};

QString fieldTypeStrCPP(int type)
{
    QStringList fieldTypeStrCPP;

    fieldTypeStrCPP << "qint8" << "qint16" << "qint32" << "quint8" << "quint16" << "quint32" << "float" << "quint8";
    return fieldTypeStrCPP[type];
}

QString fieldTypeStrCPPClass(int type)
{
    QStringList fieldTypeStrCPPClass;

    fieldTypeStrCPPClass << "INT8" << "INT16" << "INT32" << "UINT8" << "UINT16" << "UINT32" << "FLOAT32" << "ENUM";
    return fieldTypeStrCPPClass[type];
}

QString toPropertyName(const QString & name)
{
    QString str = name;

    // make sure 1st letter is upper case
    str[0] = str[0].toUpper();

    // handle underscore
    int p = str.indexOf('_');
    while (p != -1) {
        str.remove(p, 1);
        str[p] = str[p].toUpper();
        p = str.indexOf('_', p);
    }

    return str;
}

/*
 * Convert a string to lower camel case.
 * Handles following cases :
 * - Property -> property
 * - MyProperty -> myProperty
 * - MYProperty -> myProperty
 * - MY_Property -> my_Property
 * - MY -> my
 */
QString toLowerCamelCase(const QString & name)
{
    QString str = name;

    for (int i = 0; i < str.length(); ++i) {
        if (str[i].isLower() || !str[i].isLetter()) {
            break;
        }
        if (i > 0 && i < str.length() - 1) {
            // after first, look ahead one
            if (str[i + 1].isLower()) {
                break;
            }
        }
        str[i] = str[i].toLower();
    }

    return str;
}

QString toEnumName(ObjectInfo *object, FieldInfo *field, int index)
{
    QString option = field->options[index];

    if (option.contains(QRegExp(ENUM_SPECIAL_CHARS))) {
        info(object, "Enumeration value \"" + option + "\" contains special chars, cleaning.");
        option.replace(QRegExp(ENUM_SPECIAL_CHARS), "");
    }
    if (option[0].isDigit()) {
        info(object, "Enumeration value \"" + option + "\" starts with a digit, prefixing with \"" + DEFAULT_ENUM_PREFIX "\".");
        option = DEFAULT_ENUM_PREFIX + option;
    }
    if (option == option.toLower()) {
        warning(object, "Enumeration value \"" + option + "\" is all lower case, consider capitalizing.");
    }
    if (option[0].isLower()) {
        warning(object, "Enumeration value \"" + option + "\" does not start with an upper case letter.");
        option[0] = option[0].toUpper();
    }
    if (option == "FALSE") {
        warning(object, "Invalid enumeration name FALSE, converting to False.");
        option = "False";
    }
    if (option == "TRUE") {
        warning(object, "Invalid enumeration name TRUE, converting to True.");
        option = "True";
    }
    return option;
}

QString toEnumStringList(ObjectInfo *object, FieldInfo *field)
{
    QString enumListString;

    for (int m = 0; m < field->options.length(); ++m) {
        if (m > 0) {
            enumListString.append(", ");
        }
        QString option = toEnumName(object, field, m);
        enumListString.append(option);
    }

    return enumListString;
}


QString generate(Context &ctxt, const QString &fragment)
{
    QString str = fragment;

    str.replace(":ClassName", ctxt.object->name);
    str.replace(":className", ctxt.object->namelc);

    return str;
}

QString generate(Context &ctxt, FieldContext &fieldCtxt, const QString &fragment)
{
    QString str = generate(ctxt, fragment);

    str.replace(":PropName", fieldCtxt.ucPropName);
    str.replace(":propName", fieldCtxt.propName);
    str.replace(":propType", fieldCtxt.propType);
    str.replace(":propRefType", fieldCtxt.propRefType);

    str.replace(":fieldName", fieldCtxt.fieldName);
    str.replace(":fieldType", fieldCtxt.fieldType);
    str.replace(":fieldDesc", fieldCtxt.field->description);
    str.replace(":fieldUnits", fieldCtxt.field->units);
    str.replace(":fieldLimitValues", fieldCtxt.field->limitValues);

    str.replace(":elementCount", QString::number(fieldCtxt.field->numElements));
    str.replace(":enumCount", QString::number(fieldCtxt.field->numOptions));

    return str;
}

void generateFieldInfo(Context &ctxt, FieldContext &fieldCtxt)
{
    ctxt.fieldsInfo += generate(ctxt, fieldCtxt, "    // :fieldName\n");

    if (fieldCtxt.field->type == FIELDTYPE_ENUM) {
        QStringList options = fieldCtxt.field->options;
        ctxt.fieldsInfo += "    typedef enum { ";
        for (int m = 0; m < options.length(); ++m) {
            if (m > 0) {
                ctxt.fieldsInfo.append(", ");
            }
            ctxt.fieldsInfo += generate(ctxt, fieldCtxt, "%1_%2=%3")
                               .arg(fieldCtxt.field->name.toUpper())
                               .arg(options[m].toUpper().replace(QRegExp(ENUM_SPECIAL_CHARS), ""))
                               .arg(m);
        }
        ctxt.fieldsInfo += generate(ctxt, fieldCtxt, " } :fieldNameOptions;\n");
    }

    // Generate element names (only if field has more than one element)
    if (fieldCtxt.field->numElements > 1 && !fieldCtxt.field->defaultElementNames) {
        QStringList elemNames = fieldCtxt.field->elementNames;
        ctxt.fieldsInfo += "    typedef enum { ";
        for (int m = 0; m < elemNames.length(); ++m) {
            if (m > 0) {
                ctxt.fieldsInfo.append(", ");
            }
            ctxt.fieldsInfo += QString("%1_%2=%3")
                               .arg(fieldCtxt.field->name.toUpper())
                               .arg(elemNames[m].toUpper())
                               .arg(m);
        }
        ctxt.fieldsInfo += generate(ctxt, fieldCtxt, " } :fieldNameElem;\n");
    }

    // Generate array information
    if (fieldCtxt.field->numElements > 1) {
        ctxt.fieldsInfo += generate(ctxt, fieldCtxt, "    static const quint32 %1_NUMELEM = :elementCount;\n")
                           .arg(fieldCtxt.field->name.toUpper());
    }
}

void generateFieldInit(Context &ctxt, FieldContext &fieldCtxt)
{
    ctxt.fieldsInit += generate(ctxt, fieldCtxt, "    // :fieldName\n");

    // Setup element names
    ctxt.fieldsInit += generate(ctxt, fieldCtxt, "    QStringList :fieldNameElemNames;\n");
    QStringList elemNames = fieldCtxt.field->elementNames;
    ctxt.fieldsInit += generate(ctxt, fieldCtxt, "    :fieldNameElemNames");
    for (int m = 0; m < elemNames.length(); ++m) {
        ctxt.fieldsInit += QString(" << \"%1\"").arg(elemNames[m]);
    }
    ctxt.fieldsInit += ";\n";

    if (fieldCtxt.field->type == FIELDTYPE_ENUM) {
        ctxt.fieldsInit += generate(ctxt, fieldCtxt, "    QStringList :fieldNameEnumOptions;\n");
        QStringList options = fieldCtxt.field->options;
        ctxt.fieldsInit += generate(ctxt, fieldCtxt, "    :fieldNameEnumOptions");
        for (int m = 0; m < options.length(); ++m) {
            ctxt.fieldsInit += QString(" << \"%1\"").arg(options[m]);
        }
        ctxt.fieldsInit += ";\n";
        ctxt.fieldsInit += generate(ctxt, fieldCtxt,
                                    "    fields.append(new UAVObjectField(\":fieldName\", tr(\":fieldDesc\"), \":fieldUnits\", UAVObjectField::ENUM, :fieldNameElemNames, :fieldNameEnumOptions, \":fieldLimitValues\"));\n");
    } else {
        ctxt.fieldsInit += generate(ctxt, fieldCtxt,
                                    "    fields.append(new UAVObjectField(\":fieldName\", tr(\":fieldDesc\"), \":fieldUnits\", UAVObjectField::%1, :fieldNameElemNames, QStringList(), \":fieldLimitValues\"));\n")
                           .arg(fieldTypeStrCPPClass(fieldCtxt.field->type));
    }
}

void generateFieldDefault(Context &ctxt, FieldContext &fieldCtxt)
{
    if (!fieldCtxt.field->defaultValues.isEmpty()) {
        // For non-array fields
        if (fieldCtxt.field->numElements == 1) {
            ctxt.fieldsDefault += generate(ctxt, fieldCtxt, "    // :fieldName\n");
            if (fieldCtxt.field->type == FIELDTYPE_ENUM) {
                ctxt.fieldsDefault += generate(ctxt, fieldCtxt, "    data_.:fieldName = %1;\n")
                                      .arg(fieldCtxt.field->options.indexOf(fieldCtxt.field->defaultValues[0]));
            } else if (fieldCtxt.field->type == FIELDTYPE_FLOAT32) {
                ctxt.fieldsDefault += generate(ctxt, fieldCtxt, "    data_.:fieldName = %1;\n")
                                      .arg(fieldCtxt.field->defaultValues[0].toFloat());
            } else {
                ctxt.fieldsDefault += generate(ctxt, fieldCtxt, "    data_.:fieldName = %1;\n")
                                      .arg(fieldCtxt.field->defaultValues[0].toInt());
            }
        } else {
            // Initialize all fields in the array
            ctxt.fieldsDefault += generate(ctxt, fieldCtxt, "    // :fieldName\n");
            for (int idx = 0; idx < fieldCtxt.field->numElements; ++idx) {
                if (fieldCtxt.field->type == FIELDTYPE_ENUM) {
                    ctxt.fieldsDefault += generate(ctxt, fieldCtxt, "    data_.:fieldName[%1] = %2;\n")
                                          .arg(idx)
                                          .arg(fieldCtxt.field->options.indexOf(fieldCtxt.field->defaultValues[idx]));
                } else if (fieldCtxt.field->type == FIELDTYPE_FLOAT32) {
                    ctxt.fieldsDefault += generate(ctxt, fieldCtxt, "    data_.:fieldName[%1] = %2;\n")
                                          .arg(idx)
                                          .arg(fieldCtxt.field->defaultValues[idx].toFloat());
                } else {
                    ctxt.fieldsDefault += generate(ctxt, fieldCtxt, "    data_.:fieldName[%1] = %2;\n")
                                          .arg(idx)
                                          .arg(fieldCtxt.field->defaultValues[idx].toInt());
                }
            }
        }
    }
}

void generateField(Context &ctxt, FieldContext &fieldCtxt)
{
    if (fieldCtxt.field->numElements > 1) {
        ctxt.fields += generate(ctxt, fieldCtxt, "        :fieldType :fieldName[:elementCount];\n");
    } else {
        ctxt.fields += generate(ctxt, fieldCtxt, "        :fieldType :fieldName;\n");
    }
    generateFieldInfo(ctxt, fieldCtxt);
    generateFieldInit(ctxt, fieldCtxt);
    generateFieldDefault(ctxt, fieldCtxt);
}

void generateEnum(Context &ctxt, FieldContext &fieldCtxt)
{
    Q_ASSERT(fieldCtxt.field->type == FIELDTYPE_ENUM);

    QString enumStringList = toEnumStringList(ctxt.object, fieldCtxt.field);

    ctxt.enums += generate(ctxt, fieldCtxt,
                           "class :ClassName_:PropName : public QObject {\n"
                           "    Q_OBJECT\n"
                           "public:\n"
                           "    enum Enum { %1 };\n"
                           "    Q_ENUMS(Enum) // TODO switch to Q_ENUM once on Qt 5.5\n"
                           "};\n\n").arg(enumStringList);

    ctxt.enumsCount   += generate(ctxt, fieldCtxt, ":PropNameCount = :enumCount, ");

    ctxt.registerImpl += generate(ctxt, fieldCtxt,
                                  "    qmlRegisterType<:ClassName_:PropName>(\"%1.:ClassName\", 1, 0, \":PropName\");\n").arg("UAVTalk");
}

void generateBaseProperty(Context &ctxt, FieldContext &fieldCtxt)
{
    ctxt.properties        += generate(ctxt, fieldCtxt,
                                       "    Q_PROPERTY(:propType :propName READ :propName WRITE set:PropName NOTIFY :propNameChanged)\n");

    ctxt.getters           += generate(ctxt, fieldCtxt, "    :propType :propName() const;\n");
    ctxt.setters           += generate(ctxt, fieldCtxt, "    void set:PropName(const :propRefType value);\n");

    ctxt.notifications     += generate(ctxt, fieldCtxt, "    void :propNameChanged(const :propRefType value);\n");
    ctxt.notificationsImpl += generate(ctxt, fieldCtxt, "    emit :propNameChanged(:propName());\n");

    if (DEPRECATED) {
        // generate deprecated property for retro compatibility
        if (fieldCtxt.hasDeprecatedProperty) {
            ctxt.deprecatedProperties += generate(ctxt, fieldCtxt,
                                                  "    /*DEPRECATED*/ Q_PROPERTY(:fieldType :fieldName READ get:fieldName WRITE set:fieldName NOTIFY :fieldNameChanged);\n");
        }
        if (fieldCtxt.hasDeprecatedGetter) {
            ctxt.getters += generate(ctxt, fieldCtxt,
                                     "    /*DEPRECATED*/ Q_INVOKABLE :fieldType get:fieldName() const { return static_cast<:fieldType>(:propName()); }\n");
        }
        if (fieldCtxt.hasDeprecatedSetter) {
            ctxt.setters += generate(ctxt, fieldCtxt,
                                     "    /*DEPRECATED*/ void set:fieldName(:fieldType value) { set:PropName(static_cast<:propType>(value)); }\n");
        }
        if (fieldCtxt.hasDeprecatedNotification) {
            ctxt.notifications     += generate(ctxt, fieldCtxt,
                                               "    /*DEPRECATED*/ void :fieldNameChanged(:fieldType value);\n");

            ctxt.notificationsImpl += generate(ctxt, fieldCtxt,
                                               "    /*DEPRECATED*/ emit :fieldNameChanged(get:fieldName());\n");
        }
    }
}

void generateSimpleProperty(Context &ctxt, FieldContext &fieldCtxt)
{
    if (fieldCtxt.field->type == FIELDTYPE_ENUM) {
        generateEnum(ctxt, fieldCtxt);
    }

    generateBaseProperty(ctxt, fieldCtxt);

    // getter implementation
    ctxt.propertiesImpl += generate(ctxt, fieldCtxt,
                                    ":propType :ClassName:::propName() const\n"
                                    "{\n"
                                    "   QMutexLocker locker(mutex);\n"
                                    "   return static_cast<:propType>(data_.:fieldName);\n"
                                    "}\n");

    // emitters
    QString emitters = generate(ctxt, fieldCtxt, "emit :propNameChanged(value);");

    if (fieldCtxt.hasDeprecatedNotification) {
        emitters += " ";
        emitters += generate(ctxt, fieldCtxt, "emit :fieldNameChanged(static_cast<:fieldType>(value));");
    }

    // setter implementation
    ctxt.propertiesImpl += generate(ctxt, fieldCtxt,
                                    "void :ClassName::set:PropName(const :propRefType value)\n"
                                    "{\n"
                                    "   mutex->lock();\n"
                                    "   bool changed = (data_.:fieldName != static_cast<:fieldType>(value));\n"
                                    "   data_.:fieldName = static_cast<:fieldType>(value);\n"
                                    "   mutex->unlock();\n"
                                    "   if (changed) { %1 }\n"
                                    "}\n\n").arg(emitters);
}

void generateIndexedProperty(Context &ctxt, FieldContext &fieldCtxt)
{
    if (fieldCtxt.field->type == FIELDTYPE_ENUM) {
        generateEnum(ctxt, fieldCtxt);
    }

    // indexed getter/setter
    ctxt.getters += generate(ctxt, fieldCtxt, "    Q_INVOKABLE :propType :propName(quint32 index) const;\n");
    ctxt.setters += generate(ctxt, fieldCtxt, "    Q_INVOKABLE void set:PropName(quint32 index, const :propRefType value);\n");

    // getter implementation
    ctxt.propertiesImpl += generate(ctxt, fieldCtxt,
                                    ":propType :ClassName:::propName(quint32 index) const\n"
                                    "{\n"
                                    "   QMutexLocker locker(mutex);\n"
                                    "   return static_cast<:propType>(data_.:fieldName[index]);\n"
                                    "}\n");

    // emitters
    QString emitters = generate(ctxt, fieldCtxt, "emit :propNameChanged(index, value);");

    if (fieldCtxt.hasDeprecatedNotification) {
        emitters += " ";
        emitters += generate(ctxt, fieldCtxt, "emit :fieldNameChanged(index, static_cast<:fieldType>(value));");
    }

    // setter implementation
    ctxt.propertiesImpl += generate(ctxt, fieldCtxt,
                                    "void :ClassName::set:PropName(quint32 index, const :propRefType value)\n"
                                    "{\n"
                                    "   mutex->lock();\n"
                                    "   bool changed = (data_.:fieldName[index] != static_cast<:fieldType>(value));\n"
                                    "   data_.:fieldName[index] = static_cast<:fieldType>(value);\n"
                                    "   mutex->unlock();\n"
                                    "   if (changed) { %1 }\n"
                                    "}\n\n").arg(emitters);

    ctxt.notifications += generate(ctxt, fieldCtxt, "    void :propNameChanged(quint32 index, const :propRefType value);\n");

    if (DEPRECATED) {
        // backward compatibility
        if (fieldCtxt.hasDeprecatedGetter) {
            ctxt.getters += generate(ctxt, fieldCtxt,
                                     "    /*DEPRECATED*/ Q_INVOKABLE :fieldType get:fieldName(quint32 index) const { return static_cast<:fieldType>(:propName(index)); }\n");
        }
        if (fieldCtxt.hasDeprecatedSetter) {
            ctxt.setters += generate(ctxt, fieldCtxt,
                                     "    /*DEPRECATED*/ void set:fieldName(quint32 index, :fieldType value) { set:PropName(index, static_cast<:propType>(value)); }\n");
        }

        if (fieldCtxt.hasDeprecatedNotification) {
            ctxt.notifications += generate(ctxt, fieldCtxt,
                                           "    /*DEPRECATED*/ void :fieldNameChanged(quint32 index, :fieldType value);\n");
        }
    }

    for (int elementIndex = 0; elementIndex < fieldCtxt.field->numElements; elementIndex++) {
        QString elementName = fieldCtxt.field->elementNames[elementIndex];

        QString sep;
        if (fieldCtxt.propName.right(1)[0].isDigit() && elementName[0].isDigit()) {
            info(ctxt.object, "Property \"" + fieldCtxt.propName + "\" and element \"" + elementName + "\" have digit conflict, consider fixing it.");
            sep = "_";
        }

        FieldContext elementCtxt;
        elementCtxt.field       = fieldCtxt.field;
        elementCtxt.fieldName   = fieldCtxt.fieldName + "_" + elementName;
        elementCtxt.fieldType   = fieldCtxt.fieldType;
        elementCtxt.propName    = fieldCtxt.propName + sep + elementName;
        elementCtxt.ucPropName  = fieldCtxt.ucPropName + sep + elementName;
        elementCtxt.propType    = fieldCtxt.propType;
        elementCtxt.propRefType = fieldCtxt.propRefType;
        // deprecation
        elementCtxt.hasDeprecatedProperty     = (elementCtxt.fieldName != elementCtxt.propName) && DEPRECATED;
        elementCtxt.hasDeprecatedGetter       = DEPRECATED;
        elementCtxt.hasDeprecatedSetter       = ((elementCtxt.fieldName != elementCtxt.ucPropName) || (elementCtxt.fieldType != elementCtxt.propType)) && DEPRECATED;
        elementCtxt.hasDeprecatedNotification = ((elementCtxt.fieldName != elementCtxt.propName) || (elementCtxt.fieldType != elementCtxt.propType)) && DEPRECATED;


        generateBaseProperty(ctxt, elementCtxt);

        ctxt.propertiesImpl += generate(ctxt, elementCtxt,
                                        ":propType :ClassName:::propName() const { return %1(%2); }\n").arg(fieldCtxt.propName).arg(elementIndex);

        ctxt.propertiesImpl += generate(ctxt, elementCtxt,
                                        "void :ClassName::set:PropName(const :propRefType value) { set%1(%2, value); }\n").arg(fieldCtxt.ucPropName).arg(elementIndex);
    }
}

void generateProperty(Context &ctxt, FieldContext &fieldCtxt)
{
    // do some checks
    QString fieldName = fieldCtxt.fieldName;

    if (fieldName[0].isLower()) {
        info(ctxt.object, "Field \"" + fieldName + "\" does not start with an upper case letter.");
    }

    // generate all properties
    if (fieldCtxt.field->numElements > 1) {
        generateIndexedProperty(ctxt, fieldCtxt);
    } else {
        generateSimpleProperty(ctxt, fieldCtxt);
    }
}

bool UAVObjectGeneratorGCS::generate(UAVObjectParser *parser, QString templatepath, QString outputpath)
{
    gcsCodePath        = QDir(templatepath + QString(GCS_CODE_DIR));
    gcsOutputPath      = QDir(outputpath);
    gcsOutputPath.mkpath(gcsOutputPath.absolutePath());

    gcsCodeTemplate    = readFile(gcsCodePath.absoluteFilePath("uavobject.cpp.template"));
    gcsIncludeTemplate = readFile(gcsCodePath.absoluteFilePath("uavobject.h.template"));
    QString gcsInitTemplate = readFile(gcsCodePath.absoluteFilePath("uavobjectsinit.cpp.template"));

    if (gcsCodeTemplate.isEmpty() || gcsIncludeTemplate.isEmpty() || gcsInitTemplate.isEmpty()) {
        error("Error: Failed to read gcs code templates.");
        return false;
    }

    QString objInc;
    QString gcsObjInit;

    for (int objidx = 0; objidx < parser->getNumObjects(); ++objidx) {
        ObjectInfo *object = parser->getObjectByIndex(objidx);
        process_object(object);

        Context ctxt;
        ctxt.object = object;

        objInc.append(QString("#include \"%1.h\"\n").arg(object->namelc));

        gcsObjInit += ::generate(ctxt, "    objMngr->registerObject( new :ClassName() );\n");
        gcsObjInit += ::generate(ctxt, "    :ClassName::registerQMLTypes();\n");
    }

    // Write the gcs object initialization files
    gcsInitTemplate.replace("$(OBJINC)", objInc);
    gcsInitTemplate.replace("$(OBJINIT)", gcsObjInit);

    bool res = writeFileIfDifferent(gcsOutputPath.absolutePath() + "/uavobjectsinit.cpp", gcsInitTemplate);
    if (!res) {
        error("Error: Could not write output files");
        return false;
    }

    return true;
}

/**
 * Generate the GCS object files
 *
 * TODO add getter to get enum names
 *
 * TODO handle "char" unit
 * TODO handle "bool" unit
 * TODO handle "hex" unit
 * TODO handle Vector
 */
bool UAVObjectGeneratorGCS::process_object(ObjectInfo *object)
{
    if (object == NULL) {
        return false;
    }

    // Prepare output strings
    QString outInclude = gcsIncludeTemplate;
    QString outCode    = gcsCodeTemplate;

    // Replace common tags
    replaceCommonTags(outInclude, object);
    replaceCommonTags(outCode, object);

    // to avoid name conflicts
    QStringList reservedProperties;
    reservedProperties << "Description" << "Metadata";

    Context ctxt;
    ctxt.object = object;

    ctxt.registerImpl += ::generate(ctxt,
                                    "    qmlRegisterType<:ClassName>(\"%1.:ClassName\", 1, 0, \":ClassName\");\n").arg("UAVTalk");

    ctxt.registerImpl += ::generate(ctxt,
                                    "    qmlRegisterType<:ClassNameConstants>(\"%1.:ClassName\", 1, 0, \":ClassNameConstants\");\n").arg("UAVTalk");

    for (int n = 0; n < object->fields.length(); ++n) {
        FieldInfo *field = object->fields[n];

        // field context
        FieldContext fieldCtxt;
        fieldCtxt.field      = field;

        // field properties
        fieldCtxt.fieldName  = field->name;
        fieldCtxt.fieldType  = fieldTypeStrCPP(field->type);

        fieldCtxt.ucPropName = toPropertyName(field->name);
        fieldCtxt.propName   = toLowerCamelCase(fieldCtxt.ucPropName);
        fieldCtxt.propType   = fieldCtxt.fieldType;
        if (field->type == FIELDTYPE_INT8) {
            fieldCtxt.propType = fieldTypeStrCPP(FIELDTYPE_INT16);
        } else if (field->type == FIELDTYPE_UINT8) {
            fieldCtxt.propType = fieldTypeStrCPP(FIELDTYPE_UINT16);
        } else if (field->type == FIELDTYPE_ENUM) {
            QString enumClassName = object->name + "_" + fieldCtxt.ucPropName;
            fieldCtxt.propType = enumClassName + "::Enum";
        }
        // reference type
        fieldCtxt.propRefType = fieldCtxt.propType;

        // deprecation
        fieldCtxt.hasDeprecatedProperty     = (fieldCtxt.fieldName != fieldCtxt.propName) && DEPRECATED;
        fieldCtxt.hasDeprecatedGetter       = DEPRECATED;
        fieldCtxt.hasDeprecatedSetter       = ((fieldCtxt.fieldName != fieldCtxt.ucPropName) || (fieldCtxt.fieldType != fieldCtxt.propType)) && DEPRECATED;
        fieldCtxt.hasDeprecatedNotification = ((fieldCtxt.fieldName != fieldCtxt.propName) || (fieldCtxt.fieldType != fieldCtxt.propType)) && DEPRECATED;

        generateField(ctxt, fieldCtxt);

        if (reservedProperties.contains(field->name)) {
            warning(object, "Ignoring reserved property " + field->name + ".");
            continue;
        }

        generateProperty(ctxt, fieldCtxt);
    }

    outInclude.replace("$(ENUMS)", ctxt.enums);
    outInclude.replace("$(ENUMS_COUNT)", ctxt.enumsCount);
    outInclude.replace("$(DATAFIELDS)", ctxt.fields);
    outInclude.replace("$(DATAFIELDINFO)", ctxt.fieldsInfo);
    outInclude.replace("$(PROPERTIES)", ctxt.properties);
    outInclude.replace("$(DEPRECATED_PROPERTIES)", ctxt.deprecatedProperties);
    outInclude.replace("$(PROPERTY_GETTERS)", ctxt.getters);
    outInclude.replace("$(PROPERTY_SETTERS)", ctxt.setters);
    outInclude.replace("$(PROPERTY_NOTIFICATIONS)", ctxt.notifications);

    outCode.replace("$(FIELDSINIT)", ctxt.fieldsInit);
    outCode.replace("$(FIELDSDEFAULT)", ctxt.fieldsDefault);
    outCode.replace("$(PROPERTIES_IMPL)", ctxt.propertiesImpl);
    outCode.replace("$(NOTIFY_PROPERTIES_CHANGED)", ctxt.notificationsImpl);
    outCode.replace("$(REGISTER_QML_TYPES)", ctxt.registerImpl);

    // Write the GCS code
    bool res = writeFileIfDifferent(gcsOutputPath.absolutePath() + "/" + object->namelc + ".cpp", outCode);
    if (!res) {
        error("Error: Could not write gcs output files");
        return false;
    }
    res = writeFileIfDifferent(gcsOutputPath.absolutePath() + "/" + object->namelc + ".h", outInclude);
    if (!res) {
        error("Error: Could not write gcs output files");
        return false;
    }

    return true;
}
