#include "addressbook.h"
#include "ui_addressbook.h"
#include "ui_mainwindow.h"
#include "settings.h"
#include "mainwindow.h"


AddressBookModel::AddressBookModel(QTableView *parent)
     : QAbstractTableModel(parent) {
    headers << tr("Label") << tr("Address");

    this->parent = parent;
    loadData();
}

AddressBookModel::~AddressBookModel() {
    saveData();
}

void AddressBookModel::saveData() {
    // Save column positions
    QSettings().setValue("addresstablegeometry", parent->horizontalHeader()->saveState());
}


void AddressBookModel::loadData() {        
    labels = AddressBook::getInstance()->getAllAddressLabels();

    parent->horizontalHeader()->restoreState(QSettings().value("addresstablegeometry").toByteArray());
}

void AddressBookModel::addNewLabel(QString label, QString addr) {
    //labels.push_back(QPair<QString, QString>(label, addr));
    AddressBook::getInstance()->addAddressLabel(label, addr);

    labels.clear();
    labels = AddressBook::getInstance()->getAllAddressLabels();

    dataChanged(index(0, 0), index(labels.size()-1, columnCount(index(0,0))-1));
    layoutChanged();
}

void AddressBookModel::removeItemAt(int row) {
    if (row >= labels.size())
        return;

    AddressBook::getInstance()->removeAddressLabel(labels[row].first, labels[row].second);
    
    labels.clear();
    labels = AddressBook::getInstance()->getAllAddressLabels();

    dataChanged(index(0, 0), index(labels.size()-1, columnCount(index(0,0))-1));
    layoutChanged();
}

QPair<QString, QString> AddressBookModel::itemAt(int row) {
    if (row >= labels.size()) return QPair<QString, QString>();

    return labels.at(row);
}


int AddressBookModel::rowCount(const QModelIndex&) const {
    return labels.size();
}

int AddressBookModel::columnCount(const QModelIndex&) const {
    return headers.size();
}


QVariant AddressBookModel::data(const QModelIndex &index, int role) const {
    if (role == Qt::DisplayRole) {
        switch(index.column()) {
            case 0: return labels.at(index.row()).first;
            case 1: return labels.at(index.row()).second;
        }
    }
    return QVariant();
}  


QVariant AddressBookModel::headerData(int section, Qt::Orientation orientation, int role) const {
    if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
        return headers.at(section);
    }

    return QVariant();
}


//===============
// AddressBook
//===============
void AddressBook::open(MainWindow* parent, QLineEdit* target) {
    QDialog d(parent);
    Ui_addressBook ab;
    ab.setupUi(&d);
    Settings::saveRestore(&d);

    QRegExpValidator v(QRegExp(Settings::labelRegExp), ab.label);
    ab.label->setValidator(&v);

    AddressBookModel model(ab.addresses);
    ab.addresses->setModel(&model);

    // If there is no target, the we'll call the button "Ok", else "Pick"
    if (target != nullptr) {
        ab.buttonBox->button(QDialogButtonBox::Ok)->setText(QObject::tr("Pick"));
    } 

    // Connect the dialog's closing to updating the label address completor
    QObject::connect(&d, &QDialog::finished, [=] (auto) { parent->updateLabelsAutoComplete(); });

    // If there is a target then make it the addr for the "Add to" button
    if (target != nullptr && Settings::isValidAddress(target->text())) {
        ab.addr->setText(target->text());
        ab.label->setFocus();
    }

    // Add new address button
    QObject::connect(ab.addNew, &QPushButton::clicked, [&] () {
        auto addr = ab.addr->text().trimmed();
        if (!addr.isEmpty() && !ab.label->text().isEmpty()) {
            // Test if address is valid.
            if (!Settings::isValidAddress(addr)) {
                QMessageBox::critical(parent, QObject::tr("Address Format Error"), addr + QObject::tr(" doesn't seem to be a valid Zcash address."), QMessageBox::Ok);
            } else {
                model.addNewLabel(ab.label->text(), ab.addr->text());
            }
        }
    });

    // Import Button
    QObject::connect(ab.btnImport, &QPushButton::clicked, [&] () {
        // Get the import file name.
        auto fileName = QFileDialog::getOpenFileUrl(&d, QObject::tr("Import Address Book"), QUrl(), 
            "CSV file (*.csv)");
        if (fileName.isEmpty())
            return;

        QFile file(fileName.toLocalFile());
        if (!file.open(QIODevice::ReadOnly)) {
            QMessageBox::information(&d, QObject::tr("Unable to open file"), file.errorString());
            return;
        }        

        QTextStream in(&file);
        QString line;
        int numImported = 0;
        while (in.readLineInto(&line)) {
            QStringList items = line.split(",");
            if (items.size() != 2)
                continue;

            if (!Settings::isValidAddress(items.at(0)))
                continue;

            // Add label, address.
            model.addNewLabel(items.at(1), items.at(0));
            numImported++;
        }

        QMessageBox::information(&d, QObject::tr("Address Book Import Done"),
            QObject::tr("Imported %1 new Address book entries").arg(numImported));
    });

    auto fnSetTargetLabelAddr = [=] (QLineEdit* target, QString label, QString addr) {
        target->setText(label % "/" % addr);
    };

    // Double-Click picks the item
    QObject::connect(ab.addresses, &QTableView::doubleClicked, [&] (auto index) {
        // If there's no target, then double-clicking does nothing.
        if (!target)
            return;

        if (index.row() < 0) return;

        QString lbl  = model.itemAt(index.row()).first;
        QString addr = model.itemAt(index.row()).second;
        d.accept();
        fnSetTargetLabelAddr(target, lbl, addr);
    });

    // Right-Click
    ab.addresses->setContextMenuPolicy(Qt::CustomContextMenu);
    QObject::connect(ab.addresses, &QTableView::customContextMenuRequested, [&] (QPoint pos) {
        QModelIndex index = ab.addresses->indexAt(pos);

        if (index.row() < 0) return;

        QString lbl  = model.itemAt(index.row()).first;
        QString addr = model.itemAt(index.row()).second;

        QMenu menu(parent);

        if (target != nullptr) {
            menu.addAction("Pick", [&] () {
                d.accept();
                fnSetTargetLabelAddr(target, lbl, addr);
            });
        }

        menu.addAction(QObject::tr("Copy address"), [&] () {
            QGuiApplication::clipboard()->setText(addr);            
            parent->ui->statusBar->showMessage(QObject::tr("Copied to clipboard"), 3 * 1000);
        });

        menu.addAction(QObject::tr("Delete label"), [&] () {
            model.removeItemAt(index.row());
        });

        menu.exec(ab.addresses->viewport()->mapToGlobal(pos));    
    });

    if (d.exec() == QDialog::Accepted && target != nullptr) {
        auto selection = ab.addresses->selectionModel();
        if (selection->hasSelection()) {
            auto item = model.itemAt(selection->selectedRows().at(0).row());
            fnSetTargetLabelAddr(target, item.first, item.second);
        }
    };
}

//=============
// AddressBook singleton class
//=============
AddressBook* AddressBook::getInstance() {
    if (!instance)
        instance = new AddressBook();

    return instance;
}

AddressBook::AddressBook() {
    readFromStorage();
}

void AddressBook::readFromStorage() {
    QFile file(AddressBook::writeableFile());

    if (!file.exists()) {
        return;
    }

    allLabels.clear();
    file.open(QIODevice::ReadOnly);
    QDataStream in(&file);    // read the data serialized from the file
    QString version;
    in >> version >> allLabels; 

    file.close();
}

void AddressBook::writeToStorage() {
    if (allLabels.isEmpty())
        return;

    QFile file(AddressBook::writeableFile());
    file.open(QIODevice::ReadWrite | QIODevice::Truncate);
    QDataStream out(&file);   // we will serialize the data into the file
    out << QString("v1") << allLabels;
    file.close();
}

QString AddressBook::writeableFile() {
    auto filename = QStringLiteral("addresslabels.dat");

    auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
    if (!dir.exists())
        QDir().mkpath(dir.absolutePath());

    if (Settings::getInstance()->isTestnet()) {
        return dir.filePath("testnet-" % filename);
    } else {
        return dir.filePath(filename);
    }
}


// Add a new address/label to the database
void AddressBook::addAddressLabel(QString label, QString address) {
    Q_ASSERT(Settings::isValidAddress(address));

    // First, remove any existing label
    // Iterate over the list and remove the label/address
    for (int i=0; i < allLabels.size(); i++) {
        if (allLabels[i].first == label) {
            removeAddressLabel(allLabels[i].first, allLabels[i].second);
        }
    }

    allLabels.push_back(QPair<QString, QString>(label, address));
    writeToStorage();
}

// Remove a new address/label from the database
void AddressBook::removeAddressLabel(QString label, QString address) {
    // Iterate over the list and remove the label/address
    for (int i=0; i < allLabels.size(); i++) {
        if (allLabels[i].first == label && allLabels[i].second == address) {
            allLabels.removeAt(i);
            writeToStorage();
            return;
        }
    }
}

void AddressBook::updateLabel(QString oldlabel, QString address, QString newlabel) {
    // Iterate over the list and update the label/address
    for (int i = 0; i < allLabels.size(); i++) {
        if (allLabels[i].first == oldlabel && allLabels[i].second == address) {
            allLabels[i].first = newlabel;
            writeToStorage();
            return;
        }
    }
}

// Read all addresses
const QList<QPair<QString, QString>>& AddressBook::getAllAddressLabels() {
    if (allLabels.isEmpty()) {
        readFromStorage();
    }
    return allLabels;
}

// Get the label for an address
QString AddressBook::getLabelForAddress(QString addr) {
    for (auto i : allLabels) {
        if (i.second == addr)
            return i.first;
    }

    return "";
}

QString AddressBook::addLabelToAddress(QString addr) {
    QString label = AddressBook::getInstance()->getLabelForAddress(addr);
    if (!label.isEmpty())
        return label + "/" + addr;
    else
        return addr;
}

QString AddressBook::addressFromAddressLabel(const QString& lblAddr) { 
    return lblAddr.trimmed().split("/").last(); 
}

AddressBook* AddressBook::instance = nullptr;
