/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "entry_point.h"

#include <stdio.h>

#include <sstream>
#include <string>

#include "parser.h"
#include "str_utils.h"
#include "type_factory.h"

EntryPoint::EntryPoint() { reset(); }

EntryPoint::~EntryPoint() {}

void EntryPoint::reset() {
    m_unsupported = false;
    m_customDecoder = false;
    m_notApi = false;
    m_flushOnEncode = false;
    m_vars.clear();
}

// return true for valid line (need to get into the entry points list)
bool EntryPoint::parse(unsigned int lc, const std::string& str) {
    size_t pos, last;
    std::string field;

    reset();
    std::string linestr = trim(str);

    if (linestr.size() == 0) return false;
    if (linestr.at(0) == '#') return false;

    // skip PREFIX
    field = getNextToken(linestr, 0, &last, "(");
    pos = last + 1;
    // return type
    field = getNextToken(linestr, pos, &last, ",)");

    std::string error;
    std::string retTypeName;
    if (!parseTypeDeclaration(field, &retTypeName, &error)) {
        fprintf(stderr, "line: %d: Parsing error in field <%s>: %s\n", lc, field.c_str(),
                error.c_str());
        return false;
    }
    pos = last + 1;
    const VarType* theType = TypeFactory::instance()->getVarTypeByName(retTypeName);
    if (theType->name() == "UNKNOWN") {
        fprintf(stderr, "UNKNOWN retval: %s\n", linestr.c_str());
    }

    m_retval.init(std::string(""), theType, std::string(""), Var::POINTER_OUT, std::string(""),
                  std::string(""), std::string(""));

    // function name
    m_name = getNextToken(linestr, pos, &last, ",)");
    pos = last + 1;

    // parameters;
    int nvars = 0;
    while (pos < linestr.size() - 1) {
        field = getNextToken(linestr, pos, &last, ",)");
        if (field == "void") {
            // 'void' is used as a special case for functions that don't take
            // parameters at all.
            break;
        }
        std::string vartype, varname;
        if (!parseParameterDeclaration(field, &vartype, &varname, &error)) {
            fprintf(stderr, "line: %d: Parsing error in field <%s> (%s)\n", lc, field.c_str(),
                    error.c_str());
            return false;
        }
        nvars++;
        const VarType* v = TypeFactory::instance()->getVarTypeByName(vartype);
        if (v->id() == 0) {
            fprintf(stderr, "%d: Unknown type: %s\n", lc, vartype.c_str());
        } else {
            if (varname == "" && !(v->name() == "void" && !v->isPointer())) {
                std::ostringstream oss;
                oss << "var" << nvars;
                varname = oss.str();
            }

            // Custom handling of data length for the matrix data types
            std::string lenExpression = "";
            if (vartype == "mat4x4_ptr" || vartype == "const mat4x4_ptr") {
                lenExpression = "512";
            }

            m_vars.push_back(Var(varname, v, lenExpression, Var::POINTER_IN, "", "", ""));
        }
        pos = last + 1;
    }
    return true;
}

void EntryPoint::print(FILE* fp, bool newline, const std::string& name_suffix,
                       const std::string& name_prefix, const std::string& ctx_param) const {
    fprintf(fp, "%s %s%s%s(", m_retval.type()->name().c_str(), name_prefix.c_str(), m_name.c_str(),
            name_suffix.c_str());

    if (ctx_param != "") fprintf(fp, "%s ", ctx_param.c_str());

    for (size_t i = 0; i < m_vars.size(); i++) {
        if (m_vars[i].isVoid()) continue;
        if (i != 0 || ctx_param != "") fprintf(fp, ", ");
        fprintf(fp, "%s %s", m_vars[i].type()->name().c_str(), m_vars[i].name().c_str());
    }
    fprintf(fp, ")%s", newline ? "\n" : "");
}

Var* EntryPoint::var(const std::string& name) {
    Var* v = NULL;
    for (size_t i = 0; i < m_vars.size(); i++) {
        if (m_vars[i].name() == name) {
            v = &m_vars[i];
            break;
        }
    }
    return v;
}

const Var* EntryPoint::var(const std::string& name) const {
    const Var* v = NULL;
    for (size_t i = 0; i < m_vars.size(); i++) {
        if (m_vars[i].name() == name) {
            v = &m_vars[i];
            break;
        }
    }
    return v;
}

bool EntryPoint::hasPointers() {
    bool pointers = false;
    if (m_retval.isPointer()) pointers = true;
    if (!pointers) {
        for (size_t i = 0; i < m_vars.size(); i++) {
            if (m_vars[i].isPointer()) {
                pointers = true;
                break;
            }
        }
    }
    return pointers;
}

int EntryPoint::validateVarAttr(const std::string& varname, size_t lc) const {
    if (varname.size() == 0) {
        fprintf(stderr, "ERROR: %u: Missing variable name in attribute\n", (unsigned int)lc);
        return -1;
    }
    const Var* v = var(varname);
    if (v == NULL) {
        fprintf(stderr, "ERROR: %u: variable %s is not a parameter of %s\n", (unsigned int)lc,
                varname.c_str(), name().c_str());
        return -2;
    }
    return 0;
}

int EntryPoint::setAttribute(const std::string& line, size_t lc) {
    size_t pos = 0;
    size_t last;
    std::string token = getNextToken(line, 0, &last, WHITESPACE);
    int err = 0;
    Var* v = nullptr;

    if (token == "len") {
        pos = last;
        std::string varname = getNextToken(line, pos, &last, WHITESPACE);
        err = validateVarAttr(varname, lc);
        if (err < 0) return err;

        // set the size expression into var
        v = var(varname);
        pos = last;
        v->setLenExpression(line.substr(pos));
    } else if (token == "param_check") {
        pos = last;
        std::string varname = getNextToken(line, pos, &last, WHITESPACE);
        err = validateVarAttr(varname, lc);
        if (err < 0) return err;

        v = var(varname);
        pos = last;
        v->setParamCheckExpression(line.substr(pos));

    } else if (token == "dir") {
        pos = last;
        std::string varname = getNextToken(line, pos, &last, WHITESPACE);
        err = validateVarAttr(varname, lc);
        if (err < 0) return err;

        v = var(varname);
        pos = last;

        std::string pointerDirStr = getNextToken(line, pos, &last, WHITESPACE);
        if (pointerDirStr.size() == 0) {
            fprintf(stderr, "ERROR: %u: missing pointer directions\n", (unsigned int)lc);
            return -3;
        }

        if (pointerDirStr == "out") {
            v->setPointerDir(Var::POINTER_OUT);
        } else if (pointerDirStr == "inout") {
            v->setPointerDir(Var::POINTER_INOUT);
        } else if (pointerDirStr == "in") {
            v->setPointerDir(Var::POINTER_IN);
        } else {
            fprintf(stderr, "ERROR: %u: unknown pointer direction %s\n", (unsigned int)lc,
                    pointerDirStr.c_str());
        }
    } else if (token == "var_flag") {
        pos = last;
        std::string varname = getNextToken(line, pos, &last, WHITESPACE);
        err = validateVarAttr(varname, lc);
        if (err < 0) return err;

        v = var(varname);
        int count = 0;
        for (;;) {
            pos = last;
            std::string flag = getNextToken(line, pos, &last, WHITESPACE);
            if (flag.size() == 0) {
                if (count == 0) {
                    fprintf(stderr, "ERROR: %u: missing flag\n", (unsigned int)lc);
                    return -3;
                }
                break;
            }
            count++;

            if (flag == "nullAllowed") {
                if (v->isPointer()) {
                    v->setNullAllowed(true);
                } else {
                    fprintf(stderr,
                            "WARNING: %u: setting nullAllowed for non-pointer variable %s\n",
                            (unsigned int)lc, v->name().c_str());
                }
            } else if (flag == "isLarge") {
                if (v->isPointer()) {
                    v->setIsLarge(true);
                } else {
                    fprintf(stderr,
                            "WARNING: %u: setting isLarge flag for a non-pointer variable %s\n",
                            (unsigned int)lc, v->name().c_str());
                }
            } else if (flag == "DMA") {
                v->setDMA(true);
            } else {
                fprintf(stderr, "WARNING: %u: unknow flag %s\n", (unsigned int)lc, flag.c_str());
            }
        }
    } else if (token == "custom_pack") {
        pos = last;
        std::string varname = getNextToken(line, pos, &last, WHITESPACE);
        err = validateVarAttr(varname, lc);
        if (err < 0) return err;

        v = var(varname);
        pos = last;
        v->setPackExpression(line.substr(pos));
    } else if (token == "custom_unpack") {
        pos = last;
        std::string varname = getNextToken(line, pos, &last, WHITESPACE);

        err = validateVarAttr(varname, lc);
        if (err < 0) return err;

        v = var(varname);
        pos = last;
        v->setUnpackExpression(line.substr(pos));
    } else if (token == "custom_host_pack_tmp_alloc") {
        pos = last;
        std::string varname = getNextToken(line, pos, &last, WHITESPACE);
        err = validateVarAttr(varname, lc);
        if (err < 0) return err;

        v = var(varname);
        if (v->pointerDir() == Var::POINTER_IN) {
            fprintf(stderr, "ERROR: %u: variable %s is not an output or inout\n", (unsigned int)lc,
                    varname.c_str());
            return -2;
        }

        pos = last;
        v->setHostPackTmpAllocExpression(line.substr(pos));
    } else if (token == "custom_host_pack") {
        pos = last;
        std::string varname = getNextToken(line, pos, &last, WHITESPACE);
        err = validateVarAttr(varname, lc);
        if (err < 0) return err;

        v = var(varname);
        if (v->pointerDir() == Var::POINTER_IN) {
            fprintf(stderr, "ERROR: %u: variable %s is not an output or inout\n", (unsigned int)lc,
                    varname.c_str());
            return -2;
        }

        pos = last;
        v->setHostPackExpression(line.substr(pos));
    } else if (token == "custom_guest_unpack") {
        pos = last;
        std::string varname = getNextToken(line, pos, &last, WHITESPACE);
        err = validateVarAttr(varname, lc);
        if (err < 0) return err;

        v = var(varname);
        if (v->pointerDir() == Var::POINTER_IN) {
            fprintf(stderr, "ERROR: %u: variable %s is not an output or inout\n", (unsigned int)lc,
                    varname.c_str());
            return -2;
        }

        pos = last;
        v->setGuestUnpackExpression(line.substr(pos));
    } else if (token == "custom_guest_pack") {
        pos = last;
        std::string varname = getNextToken(line, pos, &last, WHITESPACE);
        err = validateVarAttr(varname, lc);
        if (err < 0) return err;

        v = var(varname);
        if (v->pointerDir() == Var::POINTER_OUT) {
            fprintf(stderr, "ERROR: %u: variable %s is not an input or inout\n", (unsigned int)lc,
                    varname.c_str());
            return -2;
        }

        pos = last;
        v->setGuestPackExpression(line.substr(pos));
    } else if (token == "custom_write") {
        pos = last;
        std::string varname = getNextToken(line, pos, &last, WHITESPACE);
        err = validateVarAttr(varname, lc);
        if (err < 0) return err;

        // set the size expression into var
        v = var(varname);
        pos = last;
        v->setWriteExpression(line.substr(pos));
    } else if (token == "flag") {
        pos = last;
        std::string flag = getNextToken(line, pos, &last, WHITESPACE);
        if (flag.size() == 0) {
            fprintf(stderr, "ERROR: %u: missing flag\n", (unsigned int)lc);
            return -4;
        }

        if (flag == "unsupported") {
            setUnsupported(true);
        } else if (flag == "custom_decoder") {
            setCustomDecoder(true);
        } else if (flag == "not_api") {
            setNotApi(true);
        } else if (flag == "flushOnEncode") {
            setFlushOnEncode(true);
        } else {
            fprintf(stderr, "WARNING: %u: unknown flag %s\n", (unsigned int)lc, flag.c_str());
        }
    } else if (token == "custom_host_api") {
        pos = last;
        std::string apiname = getNextToken(line, pos, &last, WHITESPACE);
        setCustomHostApi(apiname);
    } else {
        fprintf(stderr, "WARNING: %u: unknown attribute %s\n", (unsigned int)lc, token.c_str());
    }

    return 0;
}
