#include <QDebug>
#include <limits>
#include <stdio.h>

#include "DataTable.h"
#include "chip.h"

#define TIMESTAMP_MIN std::numeric_limits<int64_t>::min()
#define TIMESTAMP_MAX std::numeric_limits<int64_t>::max()

////////////////////////////////////////////////
// DataRow
////////////////////////////////////////////////

static const Quantity QUANTITY_EMPTY = {
    UNIT_UNKNOWN,
    QUANTITY_TYPE_ERROR,
    0xBADBAD,
};

DataRow::DataRow(DataTable *table, int64_t timestamp, int n)
    : table(table), timestamp(timestamp), quantities(new Quantity[n]), emptyCounter(n), errorCounter(0) {

    for (Quantity *q = quantities; q < quantities + n; q++) {
        *q = QUANTITY_EMPTY;
    }
}

DataRow::~DataRow() { delete[] quantities; }

bool DataRow::set(int col, const Quantity &qty) {

    if (!isEmpty(col)) {
        return false;
    }

    quantities[col] = qty;

    if (qty.type == QUANTITY_TYPE_ERROR) {
        errorCounter++;
    }

    emptyCounter--;

    return true;
}

bool DataRow::clear(int col) {

    if (isEmpty(col)) {
        return false;
    }

    if (isError(col)) {
        errorCounter--;
    }

    quantities[col] = QUANTITY_EMPTY;
    emptyCounter++;

    return true;
}

bool DataRow::isEmpty(int col) const {

    Quantity *qty = &quantities[col];
    return (qty->type == QUANTITY_TYPE_ERROR && qty->value_error == QUANTITY_EMPTY.value_error);
}

bool DataRow::isEmpty(const QString &id) const { return isEmpty(table->columnByID.value(id)); }

bool DataRow::isError(int col) const {

    Quantity *qty = &quantities[col];
    return (qty->type == QUANTITY_TYPE_ERROR && qty->value_error != QUANTITY_EMPTY.value_error);
}

bool DataRow::isError(const QString &id) const { return isError(table->columnByID.value(id)); }

const Quantity &DataRow::getQuantity(int col) const { return quantities[col]; }

const Quantity &DataRow::getQuantity(const QString &id) const { return quantities[table->columnByID.value(id)]; }

int64_t DataRow::getTimestamp() const { return timestamp; }

void DataRow::print() const {

    const int n = table->getColumnCount();

    printf("%ld", timestamp);

    for (int i = 0; i < n; i++) {

        printf("\t");

        if (isEmpty(i)) {
            printf("EMPTY");
        } else if (isError(i)) {
            printf(chip_error_to_string_no_spaces(quantities[i].value_error));
        } else {
            printf("%d", quantities[i].value_int32);
        }
    }

    printf("\t[empty=%d]", emptyCounter);

    printf("\n");
}

////////////////////////////////////////////////
// DataTable
////////////////////////////////////////////////

DataTable::DataTable(const QVector<QString> &ids, bool connected) : columnCount(ids.size()), completeRow(nullptr) {

    latestQuantities.resize(columnCount);
    disconnectionTimestamps.resize(columnCount);

    for (int i = 0; i < columnCount; i++) {
        columnByID[ids[i]] = i;
        latestQuantities[i] = QUANTITY_NO_DATA;
        disconnectionTimestamps[i] = connected ? TIMESTAMP_MAX : TIMESTAMP_MIN;
    }

    disconnectionCount = connected ? 0 : columnCount;
}

DataTable::~DataTable() {

    QLinkedListIterator<DataRow *> it(rows);

    while (it.hasNext()) {
        DataRow *row = it.next();
        delete row;
    }

    if (completeRow) {
        delete completeRow;
    }
}

DataRow *DataTable::addRow(int64_t timestamp) {

    DataRow *row = new DataRow(this, timestamp, columnCount);
    rowByTimestamp.insert(timestamp, row);

    // Fill disconnected columns

    for (int col = 0; col < columnCount; col++) {
        int64_t t = disconnectionTimestamps[col];
        if (t <= timestamp) {
            row->set(col, QUANTITY_NO_DATA);
        }
    }

    // Insert row at the right spot.
    // Usually this is at the end, so we'll iterate backwards.

    QMutableLinkedListIterator<DataRow *> it(rows);
    it.toBack();

    while (it.hasPrevious()) {
        DataRow *r = it.previous();
        if (r->timestamp < timestamp) {
            it.next();
            break;
        }
    }

    it.insert(row);

    return row;
}

int DataTable::getColumnCount() const { return columnCount; }

int DataTable::getRowCount() const { return rows.size() + (completeRow ? 1 : 0); }

int DataTable::getDisconnectionCount() const { return disconnectionCount; }

void DataTable::insert(int64_t timestamp, int col, const Quantity &qty) {

    if (col < 0 || col >= columnCount) {
        return;
    }

    DataRow *row = rowByTimestamp.value(timestamp);
    if (!row) {
        row = addRow(timestamp);
    }

    row->set(col, qty);

    // Back-fill all empty quantities for this column until now

    Quantity &latestQuantity = latestQuantities[col];

    for (QLinkedListIterator<DataRow *> it(rows); it.hasNext();) {
        DataRow *r = it.next();
        if (r == row) {
            break;
        }
        r->set(col, latestQuantity);
    }

    // Update the latest quantity for this column
    latestQuantities[col] = qty;
}

void DataTable::insert(int64_t timestamp, const QString &id, const Quantity &qty) {

    insert(timestamp, columnByID.value(id, -1), qty);
}

void DataTable::disconnect(int64_t timestamp, int col) {

    if (col < 0 || col >= columnCount) {
        return;
    }

    // Mark this column as disconnected

    if (disconnectionTimestamps[col] != TIMESTAMP_MAX) {
        // Already disconnected
        return;
    }

    disconnectionTimestamps[col] = timestamp;
    disconnectionCount++;

    // Fill all empty quantities for this column

    for (QLinkedListIterator<DataRow *> it(rows); it.hasNext();) {

        DataRow *row = it.next();

        if (row->isEmpty(col)) {
            row->set(col, QUANTITY_NO_DATA);
        }
    }

    // Update the latest quantity for this column
    latestQuantities[col] = QUANTITY_NO_DATA;
}

void DataTable::disconnect(int64_t timestamp, const QString &id) { disconnect(timestamp, columnByID.value(id, -1)); }

void DataTable::connect(int64_t timestamp, int col) {

    if (col < 0 || col >= columnCount) {
        return;
    }

    // Mark this column as connected

    if (disconnectionTimestamps[col] == TIMESTAMP_MAX) {
        // Already connected
        return;
    }

    disconnectionTimestamps[col] = TIMESTAMP_MAX;
    disconnectionCount--;

    // Clear all quantities for this column since connection

    QLinkedListIterator<DataRow *> it(rows);
    it.toBack();

    while (it.hasPrevious()) {

        DataRow *row = it.previous();

        if (row->timestamp < timestamp) {
            break;
        }

        row->clear(col);
    }
}

void DataTable::connect(int64_t timestamp, const QString &id) { connect(timestamp, columnByID.value(id, -1)); }

bool DataTable::hasCompleteRow() {

    if (completeRow) {
        delete completeRow;
        completeRow = nullptr;
    }

    if (rows.size() > 0) {

        DataRow *row = rows.first();

        if (row->emptyCounter == 0) {
            completeRow = row;
            rows.removeFirst();
            rowByTimestamp.remove(row->timestamp);
            return true;
        }
    }

    return false;
}

const DataRow *DataTable::nextCompleteRow() { return completeRow; }

void DataTable::print() {

    printf("\n");
    printf("%d ROWS\n", getRowCount());

    if (completeRow) {
        completeRow->print();
    }

    QLinkedListIterator<DataRow *> it(rows);

    while (it.hasNext()) {
        it.next()->print();
    }

    printf("\n");
}
