/*
 * Copyright (C) 2017 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.
 */

#define LOG_TAG "incident_helper"

#include "ih_util.h"

#include <algorithm>
#include <sstream>
#include <unistd.h>

bool isValidChar(char c) {
    uint8_t v = (uint8_t)c;
    return (v >= (uint8_t)'a' && v <= (uint8_t)'z')
        || (v >= (uint8_t)'A' && v <= (uint8_t)'Z')
        || (v >= (uint8_t)'0' && v <= (uint8_t)'9')
        || (v == (uint8_t)'_');
}

std::string trim(const std::string& s, const std::string& charset) {
    const auto head = s.find_first_not_of(charset);
    if (head == std::string::npos) return "";

    const auto tail = s.find_last_not_of(charset);
    return s.substr(head, tail - head + 1);
}

static inline std::string toLowerStr(const std::string& s) {
    std::string res(s);
    std::transform(res.begin(), res.end(), res.begin(), ::tolower);
    return res;
}

static inline std::string trimDefault(const std::string& s) {
    return trim(s, DEFAULT_WHITESPACE);
}

static inline std::string trimHeader(const std::string& s) {
    return toLowerStr(trimDefault(s));
}

static inline bool isNumber(const std::string& s) {
    std::string::const_iterator it = s.begin();
    while (it != s.end() && std::isdigit(*it)) ++it;
    return !s.empty() && it == s.end();
}

// This is similiar to Split in android-base/file.h, but it won't add empty string
static void split(const std::string& line, std::vector<std::string>& words,
        const trans_func& func, const std::string& delimiters) {
    words.clear();  // clear the buffer before split

    size_t base = 0;
    size_t found;
    while (true) {
        found = line.find_first_of(delimiters, base);
        if (found != base) {
            std::string word = (*func) (line.substr(base, found - base));
            if (!word.empty()) {
                words.push_back(word);
            }
        }
        if (found == line.npos) break;
        base = found + 1;
    }
}

header_t parseHeader(const std::string& line, const std::string& delimiters) {
    header_t header;
    trans_func f = &trimHeader;
    split(line, header, f, delimiters);
    return header;
}

record_t parseRecord(const std::string& line, const std::string& delimiters) {
    record_t record;
    trans_func f = &trimDefault;
    split(line, record, f, delimiters);
    return record;
}

bool getColumnIndices(std::vector<int>& indices, const char** headerNames, const std::string& line) {
    indices.clear();

    size_t lastIndex = 0;
    int i = 0;
    while (headerNames[i] != NULL) {
        std::string s = headerNames[i];
        lastIndex = line.find(s, lastIndex);
        if (lastIndex == std::string::npos) {
            fprintf(stderr, "Bad Task Header: %s\n", line.c_str());
            return false;
        }
        lastIndex += s.length();
        indices.push_back(lastIndex);
        i++;
    }

    return true;
}

record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters) {
    record_t record;
    int lastIndex = 0;
    int lastBeginning = 0;
    int lineSize = (int)line.size();
    for (std::vector<int>::const_iterator it = indices.begin(); it != indices.end(); ++it) {
        int idx = *it;
        if (idx <= lastIndex) {
            // We saved up until lastIndex last time, so we should start at
            // lastIndex + 1 this time.
            idx = lastIndex + 1;
        }
        if (idx > lineSize) {
            if (lastIndex < idx && lastIndex < lineSize) {
                // There's a little bit more for us to save, which we'll do
                // outside of the loop.
                break;
            }
            // If we're past the end of the line AND we've already saved everything up to the end.
            fprintf(stderr, "index wrong: lastIndex: %d, idx: %d, lineSize: %d\n", lastIndex, idx, lineSize);
            record.clear(); // The indices are wrong, return empty.
            return record;
        }
        while (idx < lineSize && delimiters.find(line[idx++]) == std::string::npos);
        record.push_back(trimDefault(line.substr(lastIndex, idx - lastIndex)));
        lastBeginning = lastIndex;
        lastIndex = idx;
    }
    if (lineSize - lastIndex > 0) {
        int beginning = lastIndex;
        if (record.size() == indices.size()) {
            // We've already encountered all of the columns...put whatever is
            // left in the last column.
            record.pop_back();
            beginning = lastBeginning;
        }
        record.push_back(trimDefault(line.substr(beginning, lineSize - beginning)));
    }
    return record;
}

void printRecord(const record_t& record) {
    fprintf(stderr, "Record: { ");
    if (record.size() == 0) {
        fprintf(stderr, "}\n");
        return;
    }
    for(size_t i = 0; i < record.size(); ++i) {
        if(i != 0) fprintf(stderr, "\", ");
        fprintf(stderr, "\"%s", record[i].c_str());
    }
    fprintf(stderr, "\" }\n");
}

bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter) {
    const auto head = line->find_first_not_of(DEFAULT_WHITESPACE);
    if (head == std::string::npos) return false;
    int len = (int)line->length();
    int i = 0;
    int j = head;
    while (key[i] != '\0') {
        if (j >= len || key[i++] != line->at(j++)) {
            return false;
        }
    }

    if (endAtDelimiter) {
        // this means if the line only have prefix or no delimiter, we still return false.
        if (j == len || isValidChar(line->at(j))) return false;
    }

    line->assign(trimDefault(line->substr(j)));
    return true;
}

bool stripSuffix(std::string* line, const char* key, bool endAtDelimiter) {
    const auto tail = line->find_last_not_of(DEFAULT_WHITESPACE);
    if (tail == std::string::npos) return false;
    int i = 0;
    while (key[++i] != '\0'); // compute the size of the key
    int j = tail;
    while (i > 0) {
        if (j < 0 || key[--i] != line->at(j--)) {
            return false;
        }
    }

    if (endAtDelimiter) {
        // this means if the line only have suffix or no delimiter, we still return false.
        if (j < 0 || isValidChar(line->at(j))) return false;
    }

    line->assign(trimDefault(line->substr(0, j+1)));
    return true;
}

std::string behead(std::string* line, const char cut) {
    auto found = line->find_first_of(cut);
    if (found == std::string::npos) {
        std::string head = line->substr(0);
        line->assign("");
        return head;
    }
    std::string head = line->substr(0, found);
    while(line->at(found) == cut) found++; // trim more cut of the rest
    line->assign(line->substr(found));
    return head;
}

int toInt(const std::string& s) {
    return atoi(s.c_str());
}

long long toLongLong(const std::string& s) {
    return atoll(s.c_str());
}

double toDouble(const std::string& s) {
    return atof(s.c_str());
}

// ==============================================================================
Reader::Reader(const int fd)
{
    mFile = fdopen(fd, "r");
    mStatus = mFile == NULL ? "Invalid fd " + std::to_string(fd) : "";
}

Reader::~Reader()
{
    if (mFile != NULL) fclose(mFile);
}

bool Reader::readLine(std::string* line) {
    if (mFile == NULL) return false;

    char* buf = NULL;
    size_t len = 0;
    ssize_t read = getline(&buf, &len, mFile);
    if (read != -1) {
        std::string s(buf);
        line->assign(trim(s, DEFAULT_NEWLINE));
    } else if (errno == EINVAL) {
        mStatus = "Bad Argument";
    }
    free(buf);
    return read != -1;
}

bool Reader::ok(std::string* error) {
    error->assign(mStatus);
    return mStatus.empty();
}

// ==============================================================================
Table::Table(const char* names[], const uint64_t ids[], const int count)
        :mEnums(),
         mEnumValuesByName()
{
    std::map<std::string, uint64_t> fields;
    for (int i = 0; i < count; i++) {
        fields[names[i]] = ids[i];
    }
    mFields = fields;
}

Table::~Table()
{
}

void
Table::addEnumTypeMap(const char* field, const char* enumNames[], const int enumValues[], const int enumSize)
{
    if (mFields.find(field) == mFields.end()) {
        fprintf(stderr, "Field '%s' not found", field);
        return;
    }

    std::map<std::string, int> enu;
    for (int i = 0; i < enumSize; i++) {
        enu[enumNames[i]] = enumValues[i];
    }
    mEnums[field] = enu;
}

void
Table::addEnumNameToValue(const char* enumName, const int enumValue)
{
    mEnumValuesByName[enumName] = enumValue;
}

bool
Table::insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value)
{
    if (mFields.find(name) == mFields.end()) return false;

    uint64_t found = mFields[name];
    record_t repeats; // used for repeated fields
    switch ((found & FIELD_COUNT_MASK) | (found & FIELD_TYPE_MASK)) {
        case FIELD_COUNT_SINGLE | FIELD_TYPE_DOUBLE:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_FLOAT:
            proto->write(found, toDouble(value));
            break;
        case FIELD_COUNT_SINGLE | FIELD_TYPE_STRING:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_BYTES:
            proto->write(found, value);
            break;
        case FIELD_COUNT_SINGLE | FIELD_TYPE_INT64:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_SINT64:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_UINT64:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED64:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED64:
            proto->write(found, toLongLong(value));
            break;
        case FIELD_COUNT_SINGLE | FIELD_TYPE_BOOL:
            if (strcmp(toLowerStr(value).c_str(), "true") == 0 || strcmp(value.c_str(), "1") == 0) {
                proto->write(found, true);
                break;
            }
            if (strcmp(toLowerStr(value).c_str(), "false") == 0 || strcmp(value.c_str(), "0") == 0) {
                proto->write(found, false);
                break;
            }
            return false;
        case FIELD_COUNT_SINGLE | FIELD_TYPE_ENUM:
            // if the field has its own enum mapping, use this, otherwise use general name to value mapping.
            if (mEnums.find(name) != mEnums.end()) {
                if (mEnums[name].find(value) != mEnums[name].end()) {
                    proto->write(found, mEnums[name][value]);
                } else {
                    proto->write(found, 0); // TODO: should get the default enum value (Unknown)
                }
            } else if (mEnumValuesByName.find(value) != mEnumValuesByName.end()) {
                proto->write(found, mEnumValuesByName[value]);
            } else if (isNumber(value)) {
                proto->write(found, toInt(value));
            } else {
                return false;
            }
            break;
        case FIELD_COUNT_SINGLE | FIELD_TYPE_INT32:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_SINT32:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_UINT32:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED32:
        case FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED32:
            proto->write(found, toInt(value));
            break;
        // REPEATED TYPE below:
        case FIELD_COUNT_REPEATED | FIELD_TYPE_INT32:
            repeats = parseRecord(value, COMMA_DELIMITER);
            for (size_t i=0; i<repeats.size(); i++) {
                proto->write(found, toInt(repeats[i]));
            }
            break;
        case FIELD_COUNT_REPEATED | FIELD_TYPE_STRING:
            repeats = parseRecord(value, COMMA_DELIMITER);
            for (size_t i=0; i<repeats.size(); i++) {
                proto->write(found, repeats[i]);
            }
            break;
        default:
            return false;
    }
    return true;
}

// ================================================================================
Message::Message(Table* table)
        :mTable(table),
         mPreviousField(""),
         mTokens(),
         mSubMessages()
{
}

Message::~Message()
{
}

void
Message::addSubMessage(uint64_t fieldId, Message* fieldMsg)
{
    for (auto iter = mTable->mFields.begin(); iter != mTable->mFields.end(); iter++) {
        if (iter->second == fieldId) {
            mSubMessages[iter->first] = fieldMsg;
            return;
        }
    }
}

bool
Message::insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value)
{
    // If the field name can be found, it means the name is a primitive field.
    if (mTable->mFields.find(name) != mTable->mFields.end()) {
        endSession(proto);
        // The only edge case is for example ro.hardware itself is a message, so a field called "value"
        // would be defined in proto Ro::Hardware and it must be the first field.
        if (mSubMessages.find(name) != mSubMessages.end()) {
            startSession(proto, name);
            return mSubMessages[name]->insertField(proto, "value", value);
        } else {
            return mTable->insertField(proto, name, value);
        }
    }

    // Try to find the message field which is the prefix of name, so the value would be inserted
    // recursively into the submessage.
    std::string mutableName = name;
    for (auto iter = mSubMessages.begin(); iter != mSubMessages.end(); iter++) {
        std::string fieldName = iter->first;
        std::string prefix = fieldName + "_"; // underscore is the delimiter in the name
        if (stripPrefix(&mutableName, prefix.c_str())) {
            if (mPreviousField != fieldName) {
                endSession(proto);
                startSession(proto, fieldName);
            }
            return mSubMessages[fieldName]->insertField(proto, mutableName, value);
        }
    }
    // Can't find the name in proto definition, handle it separately.
    return false;
}

void
Message::startSession(ProtoOutputStream* proto, const std::string& name)
{
    uint64_t fieldId = mTable->mFields[name];
    uint64_t token = proto->start(fieldId);
    mPreviousField = name;
    mTokens.push(token);
}

void
Message::endSession(ProtoOutputStream* proto)
{
    if (mPreviousField == "") return;
    if (mSubMessages.find(mPreviousField) != mSubMessages.end()) {
        mSubMessages[mPreviousField]->endSession(proto);
    }
    proto->end(mTokens.top());
    mTokens.pop();
    mPreviousField = "";
}
