/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * 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 "demo_table_base.h"

#include <cctype>
#include <cstring>

#include "log.h"

namespace SysTuning {
namespace TraceStreamer {
struct TableContext {
    TabTemplate tmplate;
    TraceDataCache* dataCache;
    sqlite3_module module;
    std::string tableName;
};

DemoTableBase::~DemoTableBase()
{
    demoTraceDataCache_ = nullptr;
    demoCursor_ = nullptr;
}

void DemoTableBase::DemoTableRegister(sqlite3& db,
                                      TraceDataCache* cache,
                                      const std::string& demoTableName,
                                      TabTemplate tmplate)
{
    std::unique_ptr<TableContext> demoContext(std::make_unique<TableContext>());
    demoContext->dataCache = cache;
    demoContext->tmplate = tmplate;
    demoContext->tableName = demoTableName;
    sqlite3_module& demoModule = demoContext->module;
    demoModule = {0};

    auto demoCreateFn = [](sqlite3* xdb, void* pAux, int32_t argc, const char* const* argv, sqlite3_vtab** ppVTab,
                           char** pzErr) {
        Unused(argc);
        Unused(argv);
        Unused(pzErr);
        auto demoXdesc = static_cast<const TableContext*>(pAux);
        auto demoTable = demoXdesc->tmplate(demoXdesc->dataCache);
        demoTable->name_ = demoXdesc->tableName;
        if (demoTable->name_ == "process" || demoTable->name_ == "thread") {
            demoTable->demoWdataCache_ = demoXdesc->dataCache;
        }

        demoTable->DemoInit(argc, argv);
        std::string demoCreateStmt = demoTable->DemoCreateTableSql();
        TS_LOGD("xCreate table %s, statement: %s", demoTable->name_.c_str(), demoCreateStmt.c_str());
        int32_t ret = sqlite3_declare_vtab(xdb, demoCreateStmt.c_str());
        if (ret != SQLITE_OK) {
            TS_LOGE("sqlite3_declare_vtab %s faild: %s", demoTable->name_.c_str(), demoCreateStmt.c_str());
            return ret;
        }
        *ppVTab = demoTable.release();
        return SQLITE_OK;
    };

    auto demoDestroyFn = [](sqlite3_vtab* t) {
        TS_LOGD("xDestroy table %s", static_cast<DemoTableBase*>(t)->name_.c_str());
        delete static_cast<DemoTableBase*>(t);
        return SQLITE_OK;
    };
    demoModule.xCreate = demoCreateFn;
    demoModule.xConnect = demoCreateFn;
    demoModule.xDisconnect = demoDestroyFn;
    demoModule.xDestroy = demoDestroyFn;
    SetModuleCallbacks(demoModule, demoTableName);
    sqlite3_create_module_v2(&db, demoTableName.c_str(), &demoModule, demoContext.release(),
                             [](void* arg) { delete static_cast<TableContext*>(arg); });
}

void DemoTableBase::SetModuleCallbacks(sqlite3_module& demoModule, const std::string& demoTableName)
{
    demoModule.xOpen = [](sqlite3_vtab* pVTab, sqlite3_vtab_cursor** ppCursor) {
        TS_LOGD("xOpen: %s", static_cast<DemoTableBase*>(pVTab)->name_.c_str());
        return static_cast<DemoTableBase*>(pVTab)->DemoOpen(ppCursor);
    };

    demoModule.xClose = [](sqlite3_vtab_cursor* vc) {
        TS_LOGD("xClose: %s", static_cast<Cursor*>(vc)->demoTable_->name_.c_str());
        delete static_cast<Cursor*>(vc);
        return SQLITE_OK;
    };

    demoModule.xBestIndex = [](sqlite3_vtab* pVTab, sqlite3_index_info* idxInfo) {
        TS_LOGD("xBestIndex: %s %d", static_cast<DemoTableBase*>(pVTab)->name_.c_str(), idxInfo->nConstraint);
        return static_cast<DemoTableBase*>(pVTab)->DemoBestIndex(idxInfo);
    };

    demoModule.xFilter = [](sqlite3_vtab_cursor* vc, int32_t idxNum, const char* idxStr, int32_t argc,
                            sqlite3_value** argv) {
        auto* demoVc = static_cast<Cursor*>(vc);
        demoVc->Reset();
        TS_LOGD("xFilter %s: [%d]%s", static_cast<Cursor*>(vc)->demoTable_->name_.c_str(), idxNum, idxStr);
        if (demoVc->demoTable_->demoCacheIdxNum_ != idxNum) {
            demoVc->demoTable_->demoCacheConstraint_.Clear();
            demoVc->demoTable_->demoCacheConstraint_.FromString(idxStr);
            demoVc->demoTable_->demoCacheIdxNum_ = idxNum;
        }
        return demoVc->DemoFilter(demoVc->demoTable_->demoCacheConstraint_, argv);
    };

    demoModule.xNext = [](sqlite3_vtab_cursor* vc) { return static_cast<DemoTableBase::Cursor*>(vc)->Next(); };
    demoModule.xEof = [](sqlite3_vtab_cursor* vc) { return static_cast<DemoTableBase::Cursor*>(vc)->Eof(); };
    demoModule.xColumn = [](sqlite3_vtab_cursor* vc, sqlite3_context* ctx, int32_t col) {
        static_cast<DemoTableBase::Cursor*>(vc)->demoContext_ = ctx;
        return static_cast<DemoTableBase::Cursor*>(vc)->Column(col);
    };
    if (demoTableName == "process" || demoTableName == "thread") {
        demoModule.xUpdate = [](sqlite3_vtab* pVTab, int32_t argc, sqlite3_value** argv, sqlite3_int64* pRowid) {
            TS_LOGD("xUpdate: %s", static_cast<DemoTableBase*>(pVTab)->name_.c_str());
            return static_cast<DemoTableBase*>(pVTab)->DemoUpdate(argc, argv, pRowid);
        };
    }
}

std::string DemoTableBase::DemoCreateTableSql() const
{
    std::string demoStmt = "CREATE TABLE x(";
    for (const auto& col : demoTableColumn_) {
        demoStmt += " " + col.name_ + " " + col.type_;
        demoStmt += ",";
    }
    demoStmt += " PRIMARY KEY(";
    for (size_t i = 0; i < demoTablePriKey_.size(); i++) {
        if (i != 0)
            demoStmt += ", ";
        demoStmt += demoTablePriKey_.at(i);
    }
    demoStmt += ")) WITHOUT ROWID;";
    return demoStmt;
}

int32_t DemoTableBase::DemoBestIndex(sqlite3_index_info* demoIdxInfo)
{
    FilterConstraints demoFilterConstraints;
    for (int32_t i = 0; i < demoIdxInfo->nConstraint; i++) {
        const auto& constraint = demoIdxInfo->aConstraint[i];
        if (constraint.usable) {
            demoFilterConstraints.AddConstraint(i, constraint.iColumn, constraint.op);
        }
    }
    for (int32_t i = 0; i < demoIdxInfo->nOrderBy; i++) {
        demoFilterConstraints.AddOrderBy(demoIdxInfo->aOrderBy[i].iColumn, demoIdxInfo->aOrderBy[i].desc);
    }

    EstimatedIndexInfo demoEstimate = {demoIdxInfo->estimatedRows, demoIdxInfo->estimatedCost, false};
    DemoEstimateFilterCost(demoFilterConstraints, demoEstimate);
    demoIdxInfo->orderByConsumed = demoEstimate.isOrdered;
    demoIdxInfo->estimatedCost = demoEstimate.estimatedCost;
    demoIdxInfo->estimatedRows = demoEstimate.estimatedRows;

    auto cs = demoFilterConstraints.GetConstraints();
    for (size_t i = 0; i < cs.size(); i++) {
        auto& c = cs[i];
        demoIdxInfo->aConstraintUsage[c.idxInaConstraint].argvIndex = static_cast<int32_t>(i + 1);
        demoIdxInfo->aConstraintUsage[c.idxInaConstraint].omit = c.isSupport;
    }

    std::string str;
    demoFilterConstraints.ToString(str);
    char* demoPIdxStr = static_cast<char*>(sqlite3_malloc(str.size() + 1));
    std::copy(str.begin(), str.end(), demoPIdxStr);
    demoPIdxStr[str.size()] = '\0';
    demoIdxInfo->idxStr = demoPIdxStr;
    demoIdxInfo->needToFreeIdxStr = true;
    demoIdxInfo->idxNum = ++demoBestIndexNum_;

    TS_LOGD("%s DemoBestIndex return: %d: %s", name_.c_str(), demoIdxInfo->idxNum, str.c_str());
    TS_LOGD("%s, aConstraintUsage[%d]", demoIdxInfo->idxStr, demoIdxInfo->nConstraint);
    for (int32_t i = 0; i < demoIdxInfo->nConstraint; i++) {
        TS_LOGD("col: %d op: %d, argvindex: %d omit: %d", demoIdxInfo->aConstraint[i].iColumn,
                demoIdxInfo->aConstraint[i].op, demoIdxInfo->aConstraintUsage[i].argvIndex,
                demoIdxInfo->aConstraintUsage[i].omit);
    }
    TS_LOGD("estimated: %lld cost:%.3f", demoIdxInfo->estimatedRows, demoIdxInfo->estimatedCost);

    return SQLITE_OK;
}

int32_t DemoTableBase::DemoOpen(sqlite3_vtab_cursor** demoPpCursor)
{
    *demoPpCursor = static_cast<sqlite3_vtab_cursor*>(CreateCursor().release());
    return SQLITE_OK;
}

DemoTableBase::Cursor::Cursor(const TraceDataCache* demoDataCache, DemoTableBase* demoTable, uint32_t demoRowCount)
    : demoContext_(nullptr),
      demoTable_(demoTable),
      demoDataCache_(demoDataCache),
      demoIndexMap_(std::make_unique<IndexMap>(0, demoRowCount)),
      demoRowCount_(demoRowCount)
{
}

DemoTableBase::Cursor::~Cursor()
{
    demoContext_ = nullptr;
    demoDataCache_ = nullptr;
}
void DemoTableBase::Cursor::DemoFilterTS(unsigned char op, sqlite3_value* argv, const std::deque<InternalTime>& times)
{
    auto demoArgv = static_cast<uint64_t>(sqlite3_value_int64(argv));
    auto getDemoValue = [](const uint64_t& row) { return row; };
    switch (op) {
        case SQLITE_INDEX_CONSTRAINT_EQ:
            demoIndexMap_->IntersectabcEqual(times, demoArgv, getDemoValue);
            break;
        case SQLITE_INDEX_CONSTRAINT_GT:
            demoArgv++;
        case SQLITE_INDEX_CONSTRAINT_GE: {
            demoIndexMap_->IntersectGreaterEqual(times, demoArgv, getDemoValue);
            break;
        }
        case SQLITE_INDEX_CONSTRAINT_LE:
            demoArgv++;
        case SQLITE_INDEX_CONSTRAINT_LT: {
            demoIndexMap_->IntersectLessEqual(times, demoArgv, getDemoValue);
            break;
            case SQLITE_INDEX_CONSTRAINT_ISNOTNULL: {
                demoIndexMap_->RemoveNullElements(times, demoArgv);
                break;
            }
            default:
                break;
        } // end of switch (op)
    }
}

int32_t DemoTableBase::Cursor::DemoRowId(sqlite3_int64* id)
{
    if (demoDataCache_->DemoCancel() || demoIndexMap_->Eof()) {
        return SQLITE_ERROR;
    }
    *id = static_cast<sqlite3_int64>(demoIndexMap_->CurrentRow());
    return SQLITE_OK;
}
void DemoTableBase::Cursor::DemoFilterId(unsigned char op, sqlite3_value* argv)
{
    auto demoType = sqlite3_value_type(argv);
    if (demoType != SQLITE_INTEGER) {
        // other demoType consider it NULL
        demoIndexMap_->Intersect(0, 0);
        return;
    }

    auto demoArgv = static_cast<TableRowId>(sqlite3_value_int64(argv));
    switch (op) {
        case SQLITE_INDEX_CONSTRAINT_EQ:
            demoIndexMap_->Intersect(demoArgv, demoArgv + 1);
            break;
        case SQLITE_INDEX_CONSTRAINT_GE:
            demoIndexMap_->Intersect(demoArgv, demoRowCount_);
            break;
        case SQLITE_INDEX_CONSTRAINT_GT:
            demoArgv++;
            demoIndexMap_->Intersect(demoArgv, demoRowCount_);
            break;
        case SQLITE_INDEX_CONSTRAINT_LE:
            demoArgv++;
            demoIndexMap_->Intersect(0, demoArgv);
            break;
        case SQLITE_INDEX_CONSTRAINT_LT:
            demoIndexMap_->Intersect(0, demoArgv);
            break;
        default:
            // can't filter, all rows
            break;
    }
}
} // namespace TraceStreamer
} // namespace SysTuning