#include <QResizeEvent>
#include <QScrollBar>
#include <QMessageBox>

#include "QFitsMainWindow.h"
#include "QFitsWidgetContour.h"
#include "QFitsGlobal.h"
#include "QFitsHeaderView.h"
#include "imred.h"
#include "qtdpuser.h"

#define FITS_EMPTY "                                                                                "

QFitsHeaderContent::QFitsHeaderContent(QWidget *parent)
    : QTextEdit(parent) {
    keyValue = new EscLineEdit(this);
    keyValue->setText("keyword");
    keyValue->adjustSize();
    keyValue->hide();
    newKeyValue = new EscLineEdit(this);
    newKeyValue->setText("keyword");
    newKeyValue->adjustSize();
    newKeyValue->hide();

//    connect(verticalScrollBar(), SIGNAL(valueChanged(int)), SLOT(moveEditor));
    connect(keyValue, SIGNAL(escapePressed()), keyValue, SLOT(hide()));
    connect(keyValue, SIGNAL(returnPressed()), this, SLOT(changeKey()));
    connect(newKeyValue, SIGNAL(escapePressed()), newKeyValue, SLOT(hide()));
    connect(newKeyValue, SIGNAL(returnPressed()), this, SLOT(addKey()));

    oldYvalue = 0;

    connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(adjustEditorPosition(int)));

    setMouseTracking(TRUE);
}

void QFitsHeaderContent::mouseMoveEvent(QMouseEvent *m) {
    QTextEdit::ExtraSelection highlight;
    highlight.cursor = cursorForPosition(m->pos());
    QList<QTextEdit::ExtraSelection> extras;
    if (!highlight.cursor.atEnd()) {
        highlight.cursor.select(QTextCursor::LineUnderCursor);
        if (highlight.cursor.selectedText() != FITS_EMPTY) {
            highlight.format.setProperty(QTextFormat::FullWidthSelection, true);
            highlight.format.setBackground( Qt::green );

            extras << highlight;
        }
    }
    setExtraSelections(extras);
}

void QFitsHeaderContent::mouseDoubleClickEvent(QMouseEvent *m) {
    QTextCursor mycursor = cursorForPosition(m->pos());
    mycursor.select(QTextCursor::LineUnderCursor);
    QString selectedEntry = mycursor.selectedText();
    int selectedYpos = cursorRect(mycursor).y();
    int selectedHeight = cursorRect(mycursor).height();

    if (selectedEntry == FITS_EMPTY) {
        keyValue->hide();
        QFont theFont(settings.textfont, settings.textsize);
        QFontMetrics f(theFont);
        int fw = f.boundingRect(FITS_EMPTY).width();

        newKeyValue->setText(FITS_EMPTY);
        if (newKeyValue->height() > selectedHeight) selectedYpos -= (newKeyValue->height() - selectedHeight) / 2;
        newKeyValue->setGeometry(2, selectedYpos, width() - 4 - verticalScrollBar()->width(), newKeyValue->height());
        newKeyValue->setCursorPosition(0);
        newKeyValue->setSelection(79, -80);
        newKeyValue->show();
        newKeyValue->setFocus();
    } else {
        newKeyValue->hide();
        int index = selectedEntry.indexOf('=');

        if (index > 7) {
            key = selectedEntry.left(selectedEntry.indexOf('='));
        }

        // Sanity check: Guard the keywords!
        if ((index < 8)
                || (key.left(6) == "SIMPLE")
                || (key.left(5) == "NAXIS")
                || (key.left(6) == "BITPIX")
                || (key.left(8) == "XTENSION")
                || (key.left(5) == "BZERO")
                || (key.left(6) == "BSCALE")) {
            keyValue->hide();
            QMessageBox::warning(this, "QFitsView", "The selected line\n" + selectedEntry + "\ncannot be edited");
            return;
        }
        value = selectedEntry.right(80 - selectedEntry.indexOf('=') - 1);

        QFont theFont(settings.textfont, settings.textsize);
        QFontMetrics f(theFont);
        int fw = f.boundingRect(key + "=").width();

        keyValue->setText(value);
        if (keyValue->height() > selectedHeight) selectedYpos -= (keyValue->height() - selectedHeight) / 2;
        keyValue->setGeometry(fw + 2, selectedYpos, width() - fw - 4 - verticalScrollBar()->width(), keyValue->height());
        keyValue->setCursorPosition(0);
        keyValue->setSelection(value.length() - 1, -value.length());
        keyValue->show();
        keyValue->setFocus();
    }
}

void QFitsHeaderContent::changeKey() {
    QString newvalue = keyValue->text();
    // strange, but QTextEdit returns an extra space at the end
    if (newvalue.right(1) == " ") newvalue.chop(1);
    emit keyChanged(key, newvalue);
    keyValue->hide();
}

void QFitsHeaderContent::addKey() {
    int index = newKeyValue->text().indexOf('=');

    key = newKeyValue->text().left(newKeyValue->text().indexOf('=')).toUpper();

    // Sanity check: Guard the keywords!
    if ((key.left(6) == "SIMPLE")
            || (key.left(5) == "NAXIS")
            || (key.left(6) == "BITPIX")
            || (key.left(8) == "XTENSION")
            || (key.left(5) == "BZERO")
            || (key.left(6) == "BSCALE")) {
        newKeyValue->hide();
        QMessageBox::warning(this, "QFitsView", "The selected line\n" + newKeyValue->text() + "\ncannot be added");
        return;
    }
    QString newvalue = newKeyValue->text().right(newKeyValue->text().length() - newKeyValue->text().indexOf('=') - 1);
    // strange, but QTextEdit returns an extra space at the end
    if (newvalue.right(1) == " ") newvalue.chop(1);
    emit keyChanged(key, newvalue);
    newKeyValue->hide();
}

void QFitsHeaderContent::adjustEditorPosition(int newy) {
    if (keyValue->isVisible()) {
        keyValue->move(keyValue->x(), keyValue->y() + oldYvalue - newy);
    }
    if (newKeyValue->isVisible()) {
        newKeyValue->move(newKeyValue->x(), newKeyValue->y() + oldYvalue - newy);
    }
    oldYvalue = newy;
}

QFitsHeaderView::QFitsHeaderView(QWidget *parent) : QDialog(parent) {
    setWindowTitle("QFitsView - Header Display");

    editWCSButton = new QPushButton("Edit WCS", this);
    editWCSButton->adjustSize();
    connect(editWCSButton, SIGNAL(clicked()), this, SLOT(editWCS()));

    searchAgain = new QPushButton("  next  ", this);
    searchAgain->adjustSize();
    searchAgain->setEnabled(false);

    searchTerm = new QLineEdit(this);
    searchTerm->adjustSize();
    searchTerm->resize(searchTerm->width(), editWCSButton->height());

    searchLabel = new QLabel("not found ", this);
    searchLabel->adjustSize();
    searchLabel->resize(searchLabel->width(), searchTerm->height());
    searchLabel->setAlignment(Qt::AlignRight|Qt::AlignVCenter);
    searchLabel->setText("Search: ");

    connect(searchTerm, SIGNAL(textEdited(const QString &)), this, SLOT(dosearch(const QString &)));
    connect(searchAgain, SIGNAL(clicked()), this, SLOT(dosearchagain()));

    QFont theFont(settings.textfont, settings.textsize);
    QFontMetrics f(theFont);
    int pwidth = f.boundingRect("MM").width();

    content = new QFitsHeaderContent(this);
    content->setGeometry(0, editWCSButton->height(), pwidth * 44, 300);
    content->setReadOnly(true);
    content->setWordWrapMode(QTextOption::NoWrap);
    content->setMouseTracking(TRUE);

    content->setStyleSheet(content->styleSheet() + "; selection-background-color: yellow; selection-color: black; font-family: " + settings.textfont + "; font-size: " + QString::number(settings.textsize) + "pt");
//    content->setStyleSheet("selection-background-color: yellow; selection-color: black; border: none; font-family: " + settings.textfont + "; font-size: " + QString::number(settings.textsize) + "pt");

    oldfont = settings.textfont;
    oldfontsize = settings.textsize;

    scrollPosition = 0;

    closeButton = new QPushButton("  Close  ", this);
    closeButton->adjustSize();
    closeButton->move(0, content->y() + content->height());
    connect(closeButton, SIGNAL(clicked()), this, SLOT(reject()));

    connect(content, SIGNAL(keyChanged(QString,QString)), this, SLOT(changeKey(QString, QString)));

    adjustSize();
}

void QFitsHeaderView::showUp(Fits *f) {
    if (f != NULL) {
        if (buffersLock.tryLockForWrite()) {
            f->updateHeader();
            buffersLock.unlock();
        }
        buffersLock.lockForRead();
        QString header = f->GetHeader();
        buffersLock.unlock();

        QString text = "";
        for (int i = 0; i < header.length(); i += 80) {
            text += header.mid(i, 80);
            text += '\n';
        }
        text.chop(1);
        content->setText(text);
        content->verticalScrollBar()->setValue(scrollPosition);
        show();
    }
    scrollPosition = 0;
}

void QFitsHeaderView::resizeEvent(QResizeEvent *e) {
    int w = e->size().width();
    searchAgain->move(w - searchAgain->width(), 0);
    searchTerm->move(searchAgain->x() - searchTerm->width(), 0);
    searchLabel->move(searchTerm->x() - searchLabel->width(), 0);
    content->setGeometry(0, editWCSButton->height(), w, e->size().height() - editWCSButton->height() - closeButton->height() - 5);
    closeButton->move(w / 2 - closeButton->width() / 2, e->size().height() - closeButton->height() - 5);
}

void QFitsHeaderView::keyPressEvent(QKeyEvent *e) {
    if ((e->key() == Qt::Key_F) && (e->modifiers() & Qt::CTRL)) {
        searchTerm->setFocus();
    } else if (e->key() == Qt::Key_F3) {
        dosearchagain();
    } else {
        QDialog::keyPressEvent(e);
    }
//    if ((e->key() == Qt::Key_F3) && (e->modifiers() & Qt::SHIFT)) {
//        dosearchagain2(QTextDocument::FindBackward);
//    }
}

void QFitsHeaderView::dosearch(const QString &text) {
    QPalette palette = searchTerm->palette();
    if (!text.isEmpty()) {
        content->moveCursor(QTextCursor::Start);
        if (content->find(text)) {
            searchAgain->setEnabled(true);
            searchLabel->setText("Search: ");
            palette.setColor(QPalette::Active, QPalette::Base, Qt::white);
            palette.setColor(QPalette::Active, QPalette::Text, Qt::black);
        } else {
            searchLabel->setText("not found ");
            searchAgain->setEnabled(false);
            palette.setColor(QPalette::Active, QPalette::Base, Qt::red);
            palette.setColor(QPalette::Active, QPalette::Text, Qt::white);
        }
    } else {
        searchAgain->setEnabled(false);
        searchLabel->setText("Search: ");
        palette.setColor(QPalette::Active, QPalette::Base, Qt::white);
        palette.setColor(QPalette::Active, QPalette::Text, Qt::black);
    }
    searchTerm->setPalette(palette);
}

void QFitsHeaderView::dosearchagain() {
    if (!searchTerm->text().isEmpty()) {
        if (!content->find(searchTerm->text())) {
            dosearch(searchTerm->text());
        }
    }
}

void QFitsHeaderView::fontchanged() {
    if ((oldfont != settings.textfont) || (oldfontsize != settings.textsize)) {
        oldfont = settings.textfont;
        oldfontsize = settings.textsize;
        content->setStyleSheet("selection-background-color: yellow; "
                               "selection-color: black; "
                               "border: none; "
                               "font-family: " + settings.textfont + "; "
                               "font-size: " + QString::number(settings.textsize) +
                               "pt");
    }
}

void QFitsHeaderView::editWCS() {
    imRedSetWCS::showDialog(fitsMainWindow);
}

void QFitsHeaderView::changeKey(QString key, QString value) {
    double dvalue;
    bool isNum;
    QString comment;
    QString cmd;

    scrollPosition = content->verticalScrollBar()->value();

    cmd = "setfitskey " + QString(fitsMainWindow->getCurrentBufferIndex().c_str()) + ", \"" + key + "\", ";
    if (value.contains('/')) {
        comment = value.mid(value.indexOf('/') + 1).trimmed();
        value = value.left(value.indexOf('/')).trimmed();
    }
    dvalue = value.toDouble(&isNum);
    if (isNum) {
        cmd += value;
    } else {
        cmd += "\"" + value + "\"";
    }
    if (!comment.isEmpty()) {
        cmd += ", \"" + comment + "\"";
    }

    dpuser_widget->executeCommand(cmd);
}

QFitsHeaderViewExt::QFitsHeaderViewExt(QWidget *parent) : QFitsHeaderView(parent) {
    editWCSButton->hide();
    modified = false;
    readonly = true;
}

void QFitsHeaderViewExt::reject() {
    content->keyValue->hide();
    if (modified) {
        int p = fpos;
        if (QMessageBox::question(this, "Header edit", "Header has been edited! Do you want to save your changes?") == QMessageBox::Yes) {
            FILE *fd;
            fd = fopen(filename.toStdString().c_str(), "r+b");
            if (fd != NULL) {
                fseek(fd, fpos, SEEK_SET);
                fwrite(header.toStdString().c_str(), header.length(), 1, fd);
                fclose(fd);
            }
        }
    }
    QDialog::reject();
}

void QFitsHeaderViewExt::showUp(Fits *f, QString fname) {
    if (f != NULL) {
        header = f->GetHeader();
        readonly = f->isCompressed();
        modified = false;
        if (!readonly) {
            fpos = ftell(f->fd) - header.length();
            filename = fname;
        }
    }
    QString text = "";
    for (int i = 0; i < header.length(); i += 80) {
        text += header.mid(i, 80);
        text += '\n';
    }
    text.chop(1);
    content->setText(text);
    show();
    raise();
}

void QFitsHeaderViewExt::changeKey(QString key, QString value) {
    if (readonly) {
        QMessageBox::warning(this, "QFitsView", "Cannot edit header of a compressed file");
        return;
    }
    // find the key
    bool found = false;
    int pos = 0;
    QString line;
    int scrollPos = content->verticalScrollBar()->value();
    modified = true;

    while (!found && (pos < header.length())) {
        line = header.mid(pos, 80);
        if (line.left(line.indexOf('=')).trimmed() == key.trimmed()) {
            found = true;
            header.remove(pos, 80);
            line = key;
            while (line.length() < 8) line += ' ';
            line += '=';
            if (value[0] != ' ') line += ' ';
            line += value;
            while (line.length() < 80) line += ' ';
            if (line.length() > 80) line.truncate(80);
            header.insert(pos, line);
        }
        pos += 80;
    }

    if (!found) {
        pos = 0;
        // find the END line and move to the very end
        while (!found && (pos < header.length())) {
            line = header.mid(pos, 80);
            if (line.left(8) == "END     ") {
                found = true;
                header.remove(pos, 80);
                header.insert(header.length(), line);
            }
            pos += 80;
        }
        found = false;
        pos = 0;
        // find an empty line and finally insert key
        while (!found && (pos < header.length())) {
            line = header.mid(pos, 80);
            if (line == FITS_EMPTY) {
                found = true;
                header.remove(pos, 80);
                line = key;
                while (line.length() < 8) line += ' ';
                line += '=';
                if (value[0] != ' ') line += ' ';
                line += value;
                while (line.length() < 80) line += ' ';
                if (line.length() > 80) line.truncate(80);
                header.insert(pos, line);
            }
            pos += 80;
        }
    }
    showUp(NULL, "");
    content->verticalScrollBar()->setValue(scrollPos);
}
