/*
 * Copyright (c) 2020-2021 Huawei Device Co., Ltd.
 *
 * HDF is dual licensed: you can use it either under the terms of
 * the GPL, or the BSD license, at your option.
 * See the LICENSE file in the root of this repository for complete details.
 */

#include "hcs_decompiler.h"
#include <string.h>
#include "hcs_option.h"
#include "hcs_file.h"
#include "hcs_opcode.h"
#include "hcs_parser.h"
#include "hcs_log.h"
#include "hcs_compiler.h"

static ParserObject *RebuildObject(uint8_t opCode);

static bool HcsVerifyHbcFile()
{
    struct HcsFile *input = HcsSourceQueueTop();
    HbcHeader header = { 0 };
    uint32_t size = HcsSourceFileRead(input, (uint8_t*)&header, sizeof(header));
    if (size != sizeof(header)) {
        HCS_ERROR("Illegal hcb file, invalid file size");
        return false;
    }

    if (header.magicNumber != HBC_MAGIC_NUM) {
        HCS_ERROR("file %s is not a hcb file", HcsGetCurrentSourceName());
        return false;
    }
    if (header.totalSize < 0) {
        HcsOptSetAlign(true);
        header.totalSize = -header.totalSize;
    }
    uint32_t fileSize = HcsSourceFileGetSize(input);
    if (fileSize != (sizeof(header) + header.totalSize)) {
        HCS_ERROR("size mismatch, may broken file");
        return false;
    }

    HCS_INFO("Build by hcs compile %u.%u", header.versionMajor, header.versionMinor);
    HCS_INFO("hcb file total size: %d\n", header.totalSize);
    return true;
}

#define HCS_STRING_READ_BUF_LEN 256

static char *ReadCString()
{
    char buff[HCS_STRING_READ_BUF_LEN] = {'\0'};
    int32_t index = 0;
    while (HcsSourceFileRead(NULL, (uint8_t*)&buff[index], sizeof(char))) {
        if (index == HCS_STRING_READ_BUF_LEN || buff[index] == '\0') {
            break;
        }
        index++;
    }

    if (index >= HCS_STRING_READ_BUF_LEN || buff[index] != '\0') {
        HCS_ERROR("read unterminated or too long c string at %u", HcsGetSourceFilePos(NULL));
        return NULL;
    }
    uint32_t stringFullSize = index + 1;
    uint32_t alignAppendSize = HcsAlign(stringFullSize) - stringFullSize;
    if (alignAppendSize) {
        uint8_t alignReadBuffer[ALIGN_SIZE];
        if (HcsSourceFileRead(NULL, alignReadBuffer, alignAppendSize) != alignAppendSize) {
            HCS_ERROR("read c string length mismatch %u", HcsGetSourceFilePos(NULL));
            return NULL;
        }
    }
    char *str = strdup(buff);
    if (str == NULL) {
        HCS_ERROR("%s:%d OOM", __func__, __LINE__);
        return NULL;
    }
    return str;
}

static bool ReadUint64(uint64_t *value)
{
    if (HcsSourceFileRead(NULL, (uint8_t*)value, sizeof(*value)) != sizeof(*value)) {
        HCS_ERROR("fail to read uint64");
        return false;
    }
    return true;
}

static bool ReadUint32(uint32_t *value)
{
    if (HcsSourceFileRead(NULL, (uint8_t*)value, sizeof(*value)) != sizeof(*value)) {
        HCS_ERROR("fail to read uint32");
        return false;
    }

    return true;
}

static bool ReadUint16(uint16_t *value)
{
    if (HcsAlign(sizeof(uint16_t)) != sizeof(uint16_t)) {
        uint32_t readValue = 0;
        if (!ReadUint32(&readValue)) {
            return false;
        }
        *value = (uint16_t)readValue;
        return true;
    }
    if (HcsSourceFileRead(NULL, (uint8_t*)value, sizeof(*value)) != sizeof(*value)) {
        HCS_ERROR("fail to read uint16");
        return false;
    }

    return true;
}

static bool ReadUint8(uint8_t *value)
{
    if (HcsAlign(sizeof(uint8_t)) != sizeof(uint8_t)) {
        uint32_t readValue = 0;
        if (!ReadUint32(&readValue)) {
            return false;
        }
        *value = (uint8_t)readValue;
        return true;
    }

    if (HcsSourceFileRead(NULL, value, sizeof(*value)) != sizeof(*value)) {
        HCS_ERROR("fail to read uint8");
        return false;
    }

    return true;
}

static bool NextByteCode(uint32_t *byteCode)
{
    switch (HcsAlign(OPCODE_BYTE_WIDTH)) {
        case BYTE_SIZE: {
            uint8_t value = 0;
            bool ret = ReadUint8(&value);
            *byteCode = value;
            return ret;
        }
        case DWORD_SIZE:
            return ReadUint32(byteCode);
        default:
            break;
    }
    return false;
}

static ParserObject *RebuildNode()
{
    uint32_t nodeHash = HcsGetSourceFilePos(NULL) - HcsAlign(OPCODE_BYTE_WIDTH);
    char *nodeName = ReadCString();
    if (nodeName == NULL) {
        return NULL;
    }

    ParserObject *node = HcsNewConfigNode(nodeName, CONFIG_NODE_NOREF, NULL);
    if (node == NULL) {
        HcsMemFree(nodeName);
        return NULL;
    }
    uint32_t nodeSize = 0;
    if (!ReadUint32(&nodeSize)) {
        goto ERROR;
    }
    node->objectBase.hash = nodeHash;
    node->objectBase.size = nodeSize;
    uint32_t pos = HcsGetSourceFilePos(NULL); // node content start
    uint64_t nodeEnd = nodeSize + pos;
    while (pos < nodeEnd) {
        uint32_t childOpCode;
        if (!NextByteCode(&childOpCode)) {
            HCS_ERROR("broken node");
            goto ERROR;
        }
        ParserObject *child = RebuildObject(childOpCode);
        if (child == NULL) {
            goto ERROR;
        }
        HcsAstAddChild(node, child);
        pos = HcsGetSourceFilePos(NULL);
    }

    return node;
ERROR:
    HcsDeleteParserObjectTree(node);
    return NULL;
}

static ParserObject *RebuildTerm()
{
    char *termName = ReadCString();
    if (termName == NULL) {
        return NULL;
    }
    uint32_t childOpCode;
    if (!NextByteCode(&childOpCode)) {
        HCS_ERROR("broken term");
        HcsMemFree(termName);
        return NULL;
    }

    ParserObject *value = RebuildObject(childOpCode);
    if (value == NULL) {
        HcsMemFree(termName);
        return NULL;
    }
    ParserObject *term = HcsNewConfigTerm(termName, value);
    if (term == NULL) {
        HcsMemFree(termName);
        HcsAstFreeObject(value);
    }
    return term;
}

static ParserObject *RebuildArray()
{
    uint16_t arraySize = 0;
    if (!ReadUint16(&arraySize)) {
        return NULL;
    }
    ParserObject *array = HcsNewParserObject(NULL, PARSEROP_ARRAY, 0);
    if (array == NULL) {
        return NULL;
    }
    for (int32_t i = 0; i < arraySize; ++i) {
        uint32_t elementOpCode;
        if (!NextByteCode(&elementOpCode)) {
            HCS_ERROR("broken term");
            goto ERROR;
        }
        ParserObject *element = RebuildObject(elementOpCode);
        if (element == NULL) {
            goto ERROR;
        }
        HcsAstAddChild(array, element);
    }
    return array;
ERROR:
    HcsDeleteParserObjectTree(array);
    return NULL;
}

static ParserObject *RebuildNumberObject(uint8_t type)
{
    uint8_t u8Value = 0;
    uint16_t u16Value = 0;
    uint32_t u32Value = 0;
    uint64_t u64Value = 0;
    switch (type) {
        case HCS_BYTE_OP:
            if (!ReadUint8(&u8Value)) {
                return NULL;
            }
            return HcsNewParserObject(NULL, PARSEROP_UINT8, u8Value);
        case HCS_WORD_OP:
            if (!ReadUint16(&u16Value)) {
                return NULL;
            }
            return HcsNewParserObject(NULL, PARSEROP_UINT16, u16Value);
        case HCS_DWORD_OP:
            if (!ReadUint32(&u32Value)) {
                return NULL;
            }
            return HcsNewParserObject(NULL, PARSEROP_UINT32, u32Value);
        case HCS_QWORD_OP:
            if (!ReadUint64(&u64Value)) {
                return NULL;
            }
            return HcsNewParserObject(NULL, PARSEROP_UINT64, u64Value);
        default:
            break;
    }

    return NULL;
}

static ParserObject *RebuildStringObject()
{
    char *objectName = ReadCString();
    if (objectName == NULL) {
        return NULL;
    }
    ParserObject *object = HcsNewParserObject(NULL, PARSEROP_STRING, (uint64_t)objectName);
    if (object == NULL) {
        HcsMemFree(objectName);
    }
    return object;
}

static ParserObject *RebuildNodeRefObject()
{
    uint32_t refNodeHash = 0;
    if (!ReadUint32(&refNodeHash)) {
        return NULL;
    }

    return HcsNewParserObject(NULL, PARSEROP_NODEREF, refNodeHash);
}

static ParserObject *RebuildObject(uint8_t opCode)
{
    switch (opCode) {
        case HCS_NODE_OP:
            return RebuildNode();
        case HCS_TERM_OP:
            return RebuildTerm();
        case HCS_NODEREF_OP:
            return RebuildNodeRefObject();
        case HCS_BYTE_OP: /* fall-through */
        case HCS_WORD_OP: /* fall-through */
        case HCS_DWORD_OP: /* fall-through */
        case HCS_QWORD_OP:
            return RebuildNumberObject(opCode);
        case HCS_ARRAY_OP:
            return RebuildArray();
        case HCS_STRING_OP:
            return RebuildStringObject();
        default:
            break;
    }
    HCS_ERROR("unknown OpCode %u", opCode);
    return NULL;
}

static bool RebuildAst()
{
    uint32_t currByteCode = 0;
    bool ret = NextByteCode(&currByteCode);
    if (!ret || currByteCode != HCS_NODE_OP) {
        HCS_ERROR("miss root node");
        return false;
    }

    ParserObject *rootObject = RebuildObject(currByteCode);
    if (rootObject == NULL) {
        return false;
    }

    HcsSetParserRoot(rootObject);
    return true;
}

int32_t HcsDoDecompile(void)
{
    struct HcsFile *source = NULL;
    uint32_t ret = HcsOpenSourceFile(HcsGetInputFileName(), &source, "rb");
    if (ret) {
        return ret;
    }
    HcsSourceQueuePush(source);

    do {
        if (!HcsVerifyHbcFile()) {
            ret = EDECOMP;
            break;
        }

        if (!RebuildAst()) {
            HCS_ERROR("failed to rebuild ast from hcb file");
            ret = EDECOMP;
            break;
        }
        if (HcsVerbosePrint()) {
            HcsDumpAst("Rebuild");
        }
    } while (0);

    HcsSourceQueuePop();
    HcsCloseFile(source);
    if (ret == NOERR) {
        ret = HcsDecompileOutput();
    }
    HcsDeleteParserObjectTree(HcsGetParserRoot());
    HcsSetParserRoot(NULL);
    HcsSourceNameSetClean();
    return ret;
}