/**************************************************************************
 *  This file is part of QXmlEdit                                         *
 *  Copyright (C) 2012 by Luca Bellonda and individual contributors       *
 *    as indicated in the AUTHORS file                                    *
 *  lbellonda _at_ gmail.com                                              *
 *                                                                        *
 * This library is free software; you can redistribute it and/or          *
 * modify it under the terms of the GNU Library General Public            *
 * License as published by the Free Software Foundation; either           *
 * version 2 of the License, or (at your option) any later version.       *
 *                                                                        *
 * This library is distributed in the hope that it will be useful,        *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU      *
 * Library General Public License for more details.                       *
 *                                                                        *
 * You should have received a copy of the GNU Library General Public      *
 * License along with this library; if not, write to the                  *
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,       *
 * Boston, MA  02110-1301  USA                                            *
 **************************************************************************/

#include "charencodingdialog.h"
#include "ui_charencodingdialog.h"
#include <QBuffer>
#include <QTextStream>
#include "appconfig.h"

#include "utils.h"

#define UTF8_CODEC  "UTF-8"
#define WIN_1250_CODEC  "windows-1250"
#define WIN_1252_CODEC  "windows-1252"
#define WIN_1251_CODEC  "windows-1251"
#define ISO8859_15_CODEC  "ISO-8859-15"

CharEncodingDialog::CharEncodingDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::CharEncodingDialog)
{
    _inSetup = true;
    _enc1 = NULL;
    _enc2 = NULL;
    _enc3 = NULL;
    ui->setupUi(this);
    initialSetup();
    if(0 == _usedEncodingNames.size()) {
        if(Utils::askYN(this, tr("No encodings have been configured. Do you want add automatically some common encoding?"))) {
            addEncoding(UTF8_CODEC);
            addEncoding(WIN_1252_CODEC);
            addEncoding(WIN_1250_CODEC);
            addEncoding(WIN_1251_CODEC);
            addEncoding(ISO8859_15_CODEC);
            makeEncodingLabels();
            saveConfiguration();
        }
    }
    _inSetup = false ;
    on_enc1_currentIndexChanged(ui->enc1->currentIndex());
    on_enc2_currentIndexChanged(ui->enc2->currentIndex());
    on_enc3_currentIndexChanged(ui->enc3->currentIndex());
    textChanged();
}

CharEncodingDialog::~CharEncodingDialog()
{
    delete ui;
}

void CharEncodingDialog::initEncodings()
{
    Utils::setupComboEncoding(ui->encodingCombo);
    QStringList usedEncodings ;
    Config::loadStringArray(AppConfig::KEY_ENCODING_USEDCODECS, usedEncodings);
    foreach(QString encodingName, usedEncodings) {
        addEncoding(encodingName);
    }
    makeEncodingLabels();
}

void CharEncodingDialog::initialSetup()
{
    _useHexNumbers = true;
    //--------------------------------------
    _useHexNumbers = Config::getBool(AppConfig::KEY_ENCODING_USEHEXVALUES, true) ;
    ui->hexNumericValues->setCheckState(_useHexNumbers ? Qt::Checked : Qt::Unchecked);
    //-----------------1 -------------------
    _textCharsCount = 0 ;
    initEncodings();
    connect(ui->sourceText, SIGNAL(textChanged()), this, SLOT(textChanged()));
    //---2 -----------------------------------
    Utils::setupComboEncoding(ui->enc1);
    Utils::setupComboEncoding(ui->enc2);
    Utils::setupComboEncoding(ui->enc3);
    Utils::selectComboValue(ui->enc1, Config::getInt(AppConfig::KEY_ENCODING_TRADENC1, -1));
    Utils::selectComboValue(ui->enc2, Config::getInt(AppConfig::KEY_ENCODING_TRADENC2, -1));
    Utils::selectComboValue(ui->enc3, Config::getInt(AppConfig::KEY_ENCODING_TRADENC3, -1));
    //--------------------------------------
}

void CharEncodingDialog::saveConfiguration()
{
    // gets the codec names
    QStringList usedEncodings = _usedEncodingNames.toList();
    Config::saveStringArray(AppConfig::KEY_ENCODING_USEDCODECS, usedEncodings);
}

void CharEncodingDialog::addEncoding(const QString &name)
{
    if(_usedEncodingNames.contains(name)) {
        Utils::error("The selected encoding is already present");
        return ;
    }
    QTextCodec *codec = QTextCodec::codecForName(name.toLatin1());
    if(NULL != codec) {
        _usedEncodingNames.insert(name);
        _encoders.append(codec);
        int nowRows = ui->encodedTextGrid->rowCount();
        ui->encodedTextGrid->setRowCount(nowRows + 1);
        insertEncodedRow(nowRows, codec, ui->sourceText->toPlainText());
        makeEncodingLabels();
    } else {
        Utils::error(tr("The codec '%1' cannot be found").arg(name));
    }
}

void CharEncodingDialog::deleteRows()
{
    ui->encodedTextGrid->setRowCount(0);
}

void CharEncodingDialog::insertEncodedRow(const int row, QTextCodec *codec, const QString &text)
{
    _textCharsCount = text.length();
    ui->encodedTextGrid->setColumnCount(_textCharsCount);
    for(int i = 0 ; i < _textCharsCount ; i ++) {
        QChar srcChar = text.at(i);
        QString itemText ;
        if(!codec->canEncode(srcChar)) {
            itemText = tr("fail");
        } else {
            QByteArray encodedString = codec->fromUnicode(srcChar);
            int lenOfStr = encodedString.length();
            itemText = QString("%1: ").arg(lenOfStr);
            for(int j = 0 ; j < lenOfStr ; j++) {
                char charValue = encodedString.at(j);
                QString unicodeString = fmtNumber(_useHexNumbers, charValue, 2);
                if(j > 0) {
                    itemText += ", ";
                }
                itemText += unicodeString;
            }
        }
        QTableWidgetItem *itemUnicode = new QTableWidgetItem(itemText);
        setTItemAttrib(itemUnicode, codec->name());
        ui->encodedTextGrid->setItem(row, i, itemUnicode);
    }
}

void CharEncodingDialog::setTItemAttrib(QTableWidgetItem *item, const QString &data)
{
    item->setFlags(item->flags() & (~Qt::ItemIsEditable));
    item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
    item->setData(Qt::UserRole, data);
}

QString CharEncodingDialog::fmtNumber(const bool isHex, const int number, const int fieldWidth)
{
    int finalNumber = number ;//& mask ;
    if(fieldWidth == 2) {
        finalNumber &= 0x00FF;
    }
    if(isHex) {
        return QString("0x%1").arg(finalNumber, fieldWidth, 16, QChar('0'));
    } else {
        return QString("%1").arg(finalNumber, fieldWidth, 10);
    }
}

void CharEncodingDialog::insertText(const QString &text)
{
    _textCharsCount = text.length();
    ui->encodedTextGrid->setColumnCount(_textCharsCount);
    for(int i = 0 ; i < _textCharsCount ; i ++) {
        QChar theChar = text.at(i);
        QTableWidgetItem *itemText = new QTableWidgetItem(QString("'%1'").arg(theChar));
        setTItemAttrib(itemText, "");
        ui->encodedTextGrid->setItem(0, i, itemText);
        ushort unicodeValue = theChar.unicode();
        QString unicodeString = fmtNumber(_useHexNumbers, unicodeValue, 4);
        QTableWidgetItem *itemUnicode = new QTableWidgetItem(unicodeString);
        setTItemAttrib(itemUnicode, "");
        ui->encodedTextGrid->setItem(1, i, itemUnicode);
    }
}

void CharEncodingDialog::textChanged()
{
    ui->encodedTextGrid->setUpdatesEnabled(false);
    deleteRows();
    ui->encodedTextGrid->setRowCount(2 + _encoders.size());
    QString text = ui->sourceText->toPlainText();
    insertText(text);
    int index = 2 ;
    foreach(QTextCodec * codec, _encoders) {
        insertEncodedRow(index, codec, text);
        index ++;
    }
    makeEncodingLabels();
    ui->encodedTextGrid->setUpdatesEnabled(true);
    redisplayChain();
}

void CharEncodingDialog::makeEncodingLabelsInternal(QTableWidget *grid, const bool insertSource)
{
    QStringList headerLabels;
    if(insertSource) {
        headerLabels.append(tr("Source text"));
        headerLabels.append(tr("Unicode value"));
    }
    foreach(QTextCodec * codec, _encoders) {
        headerLabels.append(codec->name());
    }
    grid->setVerticalHeaderLabels(headerLabels);
}

void CharEncodingDialog::makeEncodingLabels()
{
    makeEncodingLabelsInternal(ui->encodedTextGrid, true);
}

void CharEncodingDialog::on_hexNumericValues_stateChanged(int state)
{
    _useHexNumbers = (state == Qt::Checked) ? true : false;
    Config::saveBool(AppConfig::KEY_ENCODING_USEHEXVALUES, _useHexNumbers);
    updateAllViews();
}

void CharEncodingDialog::updateAllViews()
{
    textChanged();
}

void CharEncodingDialog::on_addEncodingCmd_clicked()
{
    int newIndex = ui->encodingCombo->currentIndex();
    if(newIndex >= 0) {
        QVariant data = ui->encodingCombo->itemData(newIndex);
        int mib = data.toInt();
        QTextCodec *currentCodec = QTextCodec::codecForMib(mib);
        addEncoding(currentCodec->name());
        saveConfiguration();
    }
}

void CharEncodingDialog::on_removeEncodingCmd_clicked()
{
    bool isOk = false;
    int rowIndex = ui->encodedTextGrid->currentRow();
    if(rowIndex >= 0) {
        QTableWidgetItem *item = ui->encodedTextGrid->item(rowIndex, 0);
        if(NULL != item) {
            QVariant data = item->data(Qt::UserRole);
            QString name = data.toString();
            if(!name.isEmpty()) {
                if(_usedEncodingNames.contains(name)) {
                    _usedEncodingNames.remove(name);
                    // remove encoding
                    foreach(QTextCodec * codec, _encoders) {
                        if(codec->name() == name) {
                            _encoders.removeAll(codec);
                            ui->encodedTextGrid->removeRow(rowIndex);
                            isOk = true ;
                            saveConfiguration();
                        } //if
                    } // foreach
                }
            }
        }
    }
    if(!isOk) {
        Utils::error(this, tr("Error removing the selected encoding."));
    }
}

void CharEncodingDialog::insertChainEncodedRow(QTableWidget *table, QTextCodec *codec, QByteArray inputData, const QString &text)
{
    table->setRowCount(2);
    QStringList headerLabels;
    headerLabels.append(tr("Source text"));
    headerLabels.append(tr("Binary Values"));
    table->setVerticalHeaderLabels(headerLabels);

    int textLength = inputData.length();
    table->setColumnCount(1);
    QString binaryText ;
    for(int i = 0 ; i < textLength ; i ++) {
        char srcChar = inputData.at(i);
        if(i > 0) {
            binaryText += ", ";
        }
        binaryText += fmtNumber(_useHexNumbers, srcChar, 2);
    }

    QTableWidgetItem *itemText = new QTableWidgetItem(text);
    setTItemAttrib(itemText, text);
    table->setItem(0, 0, itemText);

    QTableWidgetItem *itemBinary = new QTableWidgetItem(binaryText);
    setTItemAttrib(itemBinary, codec->name());
    table->setItem(1, 0, itemBinary);
}

QByteArray CharEncodingDialog::doGrid(QTableWidget *grid, QTextCodec *codec, QByteArray source, const bool isFirst, const QString &src)
{
    if(NULL != codec) {
        QString translated ;
        if(isFirst) {
            translated = src ;
        } else {
            QTextStream inputBuffer(source, QIODevice::ReadOnly);
            inputBuffer.setCodec(codec);
            translated = inputBuffer.readAll();
        }

        QByteArray outArray;
        QTextStream outputStream(&outArray, QIODevice::WriteOnly);
        outputStream.setCodec(codec);
        outputStream << translated ;
        outputStream.flush();

        insertChainEncodedRow(grid, codec, outArray, translated);
        grid->resizeColumnsToContents();
        return outArray;
    } else {
        grid->setRowCount(0);
        return QByteArray();
    }
}


void CharEncodingDialog::redisplayChain()
{
    QByteArray inputData =  ui->sourceText->toPlainText().toUtf8();
    QByteArray a1 = doGrid(ui->grid1, _enc1, inputData, true, ui->sourceText->toPlainText());
    QByteArray a2 = doGrid(ui->grid2, _enc2, a1, false, "");
    doGrid(ui->grid3, _enc3, a2, false, "");
}

QTextCodec *CharEncodingDialog::encCurrentIndexChanged(int index, QComboBox *combo, const QString &key, QTextCodec** enc)
{
    if(index != -1) {
        QVariant data = combo->itemData(index);
        int mib = data.toInt();
        *enc = QTextCodec::codecForMib(mib);
        Config::saveInt(key, mib);
        redisplayChain();
        return *enc;
    }
    return NULL ;
}

void CharEncodingDialog::on_enc1_currentIndexChanged(int index)
{
    if(_inSetup) {
        return ;
    }
    encCurrentIndexChanged(index, ui->enc1, AppConfig::KEY_ENCODING_TRADENC1, &_enc1);
}

void CharEncodingDialog::on_enc2_currentIndexChanged(int index)
{
    if(_inSetup) {
        return ;
    }
    encCurrentIndexChanged(index, ui->enc2, AppConfig::KEY_ENCODING_TRADENC2, &_enc2);
}

void CharEncodingDialog::on_enc3_currentIndexChanged(int index)
{
    if(_inSetup) {
        return ;
    }
    encCurrentIndexChanged(index, ui->enc3, AppConfig::KEY_ENCODING_TRADENC3, &_enc3);
}

void CharEncodingDialog::on_numericEdit_textChanged()
{
    if(_inSetup) {
        return ;
    }
    QString entry = ui->numericEdit->text();
    QStringList entries = entry.split(" ", QString::SkipEmptyParts);
    QByteArray binaryData;
    foreach(QString anEntry, entries) {
        bool ok = false ;
        int value = anEntry.toShort(&ok, 0);
        if(ok) {
            value &= 0x00FF ;
            binaryData.append((char)value);
        } else {
            binaryData.append('?');
        }
    }
    // display encodings
    ui->charExplorerGrid->setUpdatesEnabled(false);
    ui->charExplorerGrid->setColumnCount(1);
    ui->charExplorerGrid->setRowCount(0);
    ui->charExplorerGrid->setRowCount(_encoders.size());
    int index = 0 ;
    foreach(QTextCodec * codec, _encoders) {
        insertEncodedRowValue(index, codec, binaryData);
        index ++;
    }
    makeEncodingLabelsInternal(ui->charExplorerGrid, false);
    ui->charExplorerGrid->setUpdatesEnabled(true);
}

void CharEncodingDialog::insertEncodedRowValue(const int row, QTextCodec *codec, const QByteArray &source)
{
    QString translated = codec->toUnicode(source);
    QTableWidgetItem *itemText = new QTableWidgetItem(translated);
    setTItemAttrib(itemText, "");
    ui->charExplorerGrid->setItem(row, 0, itemText);
}


void CharEncodingDialog::on_cmdGenerateSequence_clicked()
{
    QString str ;
    for(int i = 128 ; i < 256 ; i ++) {
        str += fmtNumber(_useHexNumbers, i, 2);
        str += " ";
    }
    ui->numericEdit->setText(str);
}
