/*
 * Cppcheck - A tool for static C/C++ code analysis
 * Copyright (C) 2007-2022 Cppcheck team.
 *
 * 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/>.
 */

#include "type2.h"

#include <cstring>
#include <sstream>

static int getValue(const uint8_t *data, size_t dataSize, uint8_t maxValue, bool *done = nullptr)
{
    static size_t pos;    // current "data" position
    static int dataValue; // value extracted from data
    static int ones;      // ones. This variable tracks if we need to add more stuff in "dataValue".

    // Shift more bits from "data" into "dataValue" if needed
    while (pos < dataSize && ones < 0xFFFF) {
        ones = (ones << 8) | 0xff;
        dataValue = (dataValue << 8) | data[pos];
        pos++;
    }

    if (done)
        *done = (ones == 0);

    if (maxValue == 0)
        return 0;

    // Shift out info from "dataValue" using % . Using & and >> would work but then we are limited to "power of 2" max value.
    const int ret = dataValue % maxValue;
    ones /= maxValue;
    dataValue /= maxValue;
    return ret;
}

static std::string generateExpression2_lvalue(const uint8_t *data, size_t dataSize)
{
    return "var" + std::to_string(1 + getValue(data, dataSize, 5));
}

static std::string generateExpression2_Op(const uint8_t *data, size_t dataSize, uint8_t numberOfGlobalConstants)
{
    std::ostringstream code;
    switch (getValue(data, dataSize, 3)) {
    case 0:
        code << generateExpression2_lvalue(data, dataSize);
        break;
    case 1:
        code << "globalconstant" << (1 + getValue(data, dataSize, numberOfGlobalConstants));
        break;
    case 2:
        code << (getValue(data, dataSize, 0x80) * 0x80 + getValue(data, dataSize, 0x80));
        break;
    }
    return code.str();
}

static std::string generateExpression2_Expr(const uint8_t *data, size_t dataSize, uint8_t numberOfGlobalConstants, int depth=0)
{
    ++depth;
    const int type = (depth > 3) ? 0 : getValue(data, dataSize, 3);
    const char binop[] = "=<>+-*/%&|^";
    const char *unop[] = {"++","--","()","~"};

    switch (type) {
    case 0:
        return generateExpression2_Op(data, dataSize, numberOfGlobalConstants);
    case 1: {
        const char op = binop[getValue(data,dataSize,sizeof(binop)-1)];
        const std::string lhs = (op == '=') ?
                                generateExpression2_lvalue(data, dataSize) :
                                generateExpression2_Expr(data, dataSize, numberOfGlobalConstants, depth);
        const std::string rhs = generateExpression2_Expr(data, dataSize, numberOfGlobalConstants, depth);

        std::string ret = lhs + op + rhs;
        if (depth > 1 && op == '=')
            ret = "(" + ret + ")";

        return ret;
    }
    case 2: {
        const char *u = unop[getValue(data,dataSize,sizeof(unop)/sizeof(*unop))];
        if (strcmp(u, "()") == 0)
            return "(" + generateExpression2_Expr(data, dataSize, numberOfGlobalConstants, depth) + ")";
        else if (strcmp(u, "++") == 0 || strcmp(u, "--") == 0)
            return u + generateExpression2_lvalue(data, dataSize);
        return u + generateExpression2_Expr(data, dataSize, numberOfGlobalConstants, depth);
    }
    default:
        break;
    }

    return "0";
}


static std::string generateExpression2_Cond(const uint8_t *data, size_t dataSize, uint8_t numberOfGlobalConstants)
{
    const char *comp[] = {"==", "!=", "<", "<=", ">", ">="};
    const int i = getValue(data, dataSize, 6);
    const std::string lhs = generateExpression2_Expr(data, dataSize, numberOfGlobalConstants);
    const std::string rhs = generateExpression2_Expr(data, dataSize, numberOfGlobalConstants);
    return lhs + comp[i] + rhs;
}


static std::string functionStart()
{
    static int functionNumber;
    return "int f" + std::to_string(++functionNumber) + "()\n"
           "{\n";
}

static std::string generateExpression2_conditionalCode(const std::string &indent,
                                                       const uint8_t *data,
                                                       size_t dataSize,
                                                       uint8_t numberOfGlobalConstants)
{
    std::ostringstream code;

    if (indent.empty())
        code << functionStart();
    else
        code << indent << "{\n";

    for (int line = 0; line < 4 || indent.empty(); ++line) {
        bool done = false;
        const int type1 = getValue(data, dataSize, 8, &done);
        if (done)
            break;

        const int mostLikelyType = (line >= 2) ? 4 : 0;  // should var assignment or return be more likely?

        const int type2 = (indent.size() >= 12) ?
                          mostLikelyType :  // max indentation, no inner conditions
                          ((type1 >= 5) ? mostLikelyType : type1);

        if (type2 == 0) {
            code << indent << "    var" << getValue(data, dataSize, 5) << "=" << generateExpression2_Expr(data, dataSize, numberOfGlobalConstants) << ";\n";
        } else if (type2 == 1) {
            code << indent << "    if (" << generateExpression2_Cond(data, dataSize, numberOfGlobalConstants) << ")\n";
            code << generateExpression2_conditionalCode(indent + "    ", data, dataSize, numberOfGlobalConstants);
        } else if (type2 == 2) {
            code << indent << "    if (" << generateExpression2_Cond(data, dataSize, numberOfGlobalConstants) << ")\n";
            code << generateExpression2_conditionalCode(indent + "    ", data, dataSize, numberOfGlobalConstants);
            code << indent << "    else\n";
            code << generateExpression2_conditionalCode(indent + "    ", data, dataSize, numberOfGlobalConstants);
        } else if (type2 == 3) {
            code << indent << "    while (" << generateExpression2_Cond(data, dataSize, numberOfGlobalConstants) << ")\n";
            code << generateExpression2_conditionalCode(indent + "    ", data, dataSize, numberOfGlobalConstants);
        } else if (type2 == 4) {
            code << indent << "    return " << generateExpression2_Expr(data, dataSize, numberOfGlobalConstants) << ";\n";
            if (indent.empty())
                code << "}\n\n" << functionStart();
            else
                break;
        }
    }

    if (!indent.empty())
        code << indent << "}\n";
    else
        code << "    return 0;\n}\n";
    return code.str();
}

std::string generateCode2(const uint8_t *data, size_t dataSize)
{
    std::ostringstream code;

    // create global constants
    constexpr uint8_t numberOfGlobalConstants = 0;
    /*
       const int numberOfGlobalConstants = getValue(data, dataSize, 5);
       for (int nr = 1; nr <= numberOfGlobalConstants; nr++) {
        const char *types[4] = {"char", "int", "long long", "float"};
        code << "const " << types[getValue(data, dataSize, 4)] << " globalconstant" << nr << " = " << generateExpression2_Expr(data, dataSize, nr - 1) << ";\n";
       }
     */

    code << "int var1 = 1;\n"
        "int var2 = 0;\n"
        "int var3 = 1;\n"
        "int var4 = 0;\n"
        "int var5 = -1;\n\n";

    code << generateExpression2_conditionalCode("", data, dataSize, numberOfGlobalConstants);

    return code.str();
}