#include "fb2html.h"
#include "fb2utils.h"
#include "fb2text.hpp"

//---------------------------------------------------------------------------
//  FbTextElement::Scheme
//---------------------------------------------------------------------------

FbTextElement::Scheme::Scheme()
{
    m_types["BODY"]
        << Type("FB:DESCRIPTION", 1, 1)
        << Type("FB:BODY", 1, 1)
    ;

    m_types["FB:DESCRIPTION"]
        << Type("FB:TITLE-INFO", 1, 1)
        << Type("FB:SRC-TITLE-INFO", 0, 1)
        << Type("FB:DOCUMENT-INFO", 1, 1)
        << Type("FB:PUBLISH-INFO", 0, 1)
        << Type("FB:CUSTOM-INFO")
    ;

    m_types["FB:DOCUMENT-INFO"]
        << Type("FB:AUTHOR", 1, 1)
        << Type("FB:PROGRAM-USED", 0, 1)
        << Type("FB:DATE", 0, 1)
    ;

    m_types["FB:BODY"]
        << Type("IMG")
        << Type("FB:TITLE", 0, 1)
        << Type("FB:EPIGRAPH")
        << Type("FB:SECTION", 1, 0)
    ;

    m_types["FB:SECTION"]
        << Type("FB:TITLE", 1, 0)
        << Type("FB:EPIGRAPH")
        << Type("IMG")
        << Type("FB:ANNOTATION")
        << Type("FB:SECTION")
    ;

    m_types["FB:POEM"]
        << Type("FB:TITLE", 1, 0)
        << Type("FB:EPIGRAPH", 0, 0)
        << Type("FB:STANZA", 1, 0)
    ;

    m_types["FB:STANZA"]
        << Type("FB:TITLE", 1, 0)
    ;
}

const FbTextElement::TypeList * FbTextElement::Scheme::operator[](const QString &name) const
{
    TypeMap::const_iterator it = m_types.find(name);
    if (it != m_types.end()) return &it.value();
    return 0;
}

//---------------------------------------------------------------------------
//  FbTextElement::Sublist
//---------------------------------------------------------------------------

FbTextElement::Sublist::Sublist(const TypeList &list, const QString &name)
    : m_list(list)
    , m_pos(list.begin())
{
    while (m_pos != list.end()) {
        if (m_pos->name() == name) break;
        m_pos++;
    }
}

FbTextElement::Sublist::operator bool() const
{
    return m_pos != m_list.end();
}

bool FbTextElement::Sublist::operator!() const
{
    return m_pos == m_list.end();
}

bool FbTextElement::Sublist::operator <(const FbTextElement &element) const
{
    if (element.isNull()) return true;
    const QString name = element.tagName();
    for (TypeList::const_iterator it = m_list.begin(); it != m_list.end(); it++) {
        if (it->name() == name) return m_pos < it;
    }
    return false;
}

//---------------------------------------------------------------------------
//  FbTextElement
//---------------------------------------------------------------------------

FbTextElement FbTextElement::operator[](const QString &name)
{
    QString tagName = name.toUpper();
    FbTextElement child = firstChild();
    while (!child.isNull()) {
        if (child.tagName() == tagName) return child;
        child = child.nextSibling();
    }
    return insertInside(tagName, QString("<%1></%1>").arg(name));
}

QString FbTextElement::nodeName() const
{
    QString n = tagName().toLower();
    return n.left(3) == "fb:" ? n.mid(3) : n;
}

void FbTextElement::getChildren(FbElementList &list)
{
    FbTextElement child = firstChild();
    while (!child.isNull()) {
        QString tag = child.tagName();
        if (tag == "FB:DESCRIPTION") {
            // skip description
        } else if (tag.left(3) == "FB:") {
            list << child;
        } else if (tag == "IMG") {
            list << child;
        } else {
            child.getChildren(list);
        }
        child = child.nextSibling();
    }
}

int FbTextElement::childIndex() const
{
    FbElementList list;
    parent().getChildren(list);

    int result = 0;
    FbElementList::const_iterator it;
    for (it = list.constBegin(); it != list.constEnd(); ++it) {
        if (*it == *this) return result;
        result++;
    }
    return -1;
}

bool FbTextElement::hasScheme() const
{
    return subtypes();
}

const FbTextElement::TypeList * FbTextElement::subtypes() const
{
    static Scheme scheme;
    return scheme[tagName()];
}

bool FbTextElement::hasSubtype(const QString &style) const
{
    if (const TypeList * list = subtypes()) {
        for (TypeList::const_iterator item = list->begin(); item != list->end(); item++) {
            if (item->name() == style) return true;
        }
    }
    return false;
}

FbTextElement::TypeList::const_iterator FbTextElement::subtype(const TypeList &list, const QString &style)
{
    for (TypeList::const_iterator item = list.begin(); item != list.end(); item++) {
        if (item->name() == style) return item;
    }
    return list.end();
}

FbTextElement FbTextElement::insertInside(const QString &style, const QString &html)
{
    const TypeList * types = subtypes();
    if (!types) return FbTextElement();

    Sublist sublist(*types, style);
    if (sublist) {
        FbTextElement child = firstChild();
        if (sublist < child) {
            prependInside(html);
            return firstChild();
        }
        while (!child.isNull()) {
            FbTextElement subling = child.nextSibling();
            if (sublist < subling) {
                child.appendOutside(html);
                return child.nextSibling();
            }
            child = subling;
        }
    }
    appendInside(html);
    return lastChild();
}

QString FbTextElement::location()
{
    return evaluateJavaScript("location(this)").toString();
}

void FbTextElement::select()
{
    QString javascript = jScript("set_cursor.js");
    evaluateJavaScript(javascript);
}

bool FbTextElement::hasChild(const QString &style) const
{
    FbTextElement child = firstChild();
    while (!child.isNull()) {
        if (child.tagName() == style) return true;
        child = child.nextSibling();
    }
    return false;
}

bool FbTextElement::isBody() const
{
    return tagName() == "FB:BODY";
}

bool FbTextElement::isSection() const
{
    return tagName() == "FB:SECTION";
}

bool FbTextElement::isTitle() const
{
    return tagName() == "FB:TITLE";
}

bool FbTextElement::isStanza() const
{
    return tagName() == "FB:STANZA";
}

bool FbTextElement::hasTitle() const
{
    return hasChild("FB:TITLE");
}

int FbTextElement::index() const
{
    int result = -1;
    FbTextElement prior = *this;
    while (!prior.isNull()) {
        prior = prior.previousSibling();
        result++;
    }
    return result;
}

FbTextElement FbTextElement::child(int index) const
{
    FbTextElement result = firstChild();
    while (index > 0) {
        result = result.nextSibling();
        index--;
    }
    return index ? FbTextElement() : result;
}

//---------------------------------------------------------------------------
//  FbInsertCmd
//---------------------------------------------------------------------------

FbInsertCmd::FbInsertCmd(const FbTextElement &element)
    : QUndoCommand()
    , m_element(element)
    , m_parent(element.previousSibling())
    , m_inner(false)
{
    if (m_parent.isNull()) {
        m_parent = m_element.parent();
        m_inner = true;
    }
}

void FbInsertCmd::redo()
{
    if (m_inner) {
        m_parent.prependInside(m_element);
    } else {
        m_parent.appendOutside(m_element);
    }
    m_element.select();
}

void FbInsertCmd::undo()
{
    m_element.takeFromDocument();
}

//---------------------------------------------------------------------------
//  FbReplaceCmd
//---------------------------------------------------------------------------

FbReplaceCmd::FbReplaceCmd(const FbTextElement &original, const FbTextElement &duplicate)
    : QUndoCommand()
    , m_original(original)
    , m_duplicate(duplicate)
    , m_update(false)
{
}

void FbReplaceCmd::redo()
{
    if (m_update) {
        m_original.prependOutside(m_duplicate);
        m_original.takeFromDocument();
        m_duplicate.select();
    } else {
        m_update = true;
    }
}

void FbReplaceCmd::undo()
{
    m_duplicate.prependOutside(m_original);
    m_duplicate.takeFromDocument();
    m_original.select();
}

//---------------------------------------------------------------------------
//  FbDeleteCmd
//---------------------------------------------------------------------------

FbDeleteCmd::FbDeleteCmd(const FbTextElement &element)
    : QUndoCommand()
    , m_element(element)
    , m_parent(element.previousSibling())
    , m_inner(false)
{
    if (m_parent.isNull()) {
        m_parent = element.parent();
        m_inner = true;
    }
}

void FbDeleteCmd::redo()
{
    m_element.takeFromDocument();
}

void FbDeleteCmd::undo()
{
    if (m_inner) {
        m_parent.prependInside(m_element);
    } else {
        m_parent.appendOutside(m_element);
    }
    m_element.select();
}

//---------------------------------------------------------------------------
//  FbMoveUpCmd
//---------------------------------------------------------------------------

FbMoveUpCmd::FbMoveUpCmd(const FbTextElement &element)
    : QUndoCommand()
    , m_element(element)
{
}

void FbMoveUpCmd::redo()
{
    FbTextElement subling = m_element.previousSibling();
    subling.prependOutside(m_element.takeFromDocument());
}

void FbMoveUpCmd::undo()
{
    FbTextElement subling = m_element.nextSibling();
    subling.appendOutside(m_element.takeFromDocument());
}


//---------------------------------------------------------------------------
//  FbMoveLeftCmd
//---------------------------------------------------------------------------

FbMoveLeftCmd::FbMoveLeftCmd(const FbTextElement &element)
    : QUndoCommand()
    , m_element(element)
    , m_subling(element.previousSibling())
    , m_parent(element.parent())
{
}

void FbMoveLeftCmd::redo()
{
    m_parent.appendOutside(m_element.takeFromDocument());
}

void FbMoveLeftCmd::undo()
{
    if (m_subling.isNull()) {
        m_parent.prependInside(m_element.takeFromDocument());
    } else {
        m_subling.appendOutside(m_element.takeFromDocument());
    }
}

//---------------------------------------------------------------------------
//  FbMoveRightCmd
//---------------------------------------------------------------------------

FbMoveRightCmd::FbMoveRightCmd(const FbTextElement &element)
    : QUndoCommand()
    , m_element(element)
    , m_subling(element.previousSibling())
{
}

void FbMoveRightCmd::redo()
{
    m_subling.appendInside(m_element.takeFromDocument());
}

void FbMoveRightCmd::undo()
{
    m_subling.appendOutside(m_element.takeFromDocument());
}