/***************************************************************************
 *   Copyright (C) 2014 by Petr Vanek                                      *
 *   petr@scribus.info                                                     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program 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 General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/

#include <QDebug>
#include <QStandardPaths>

#include "bookmarkswidget.h"
#include "properties.h"
#include "config.h"


class AbstractBookmarkItem
{
public:
    enum ItemType {
        Root = 0,
        Group = 1,
        Command = 2
    };

    AbstractBookmarkItem(AbstractBookmarkItem* parent = 0)
    {
        m_parent = parent;
    }
    ~AbstractBookmarkItem()
    {
        qDeleteAll(m_children);
    }

    ItemType type() { return m_type; }
    QString value() { return m_value; }
    QString display() { return m_display; }

    void addChild(AbstractBookmarkItem* item) { m_children << item; }
    int childCount() { return m_children.count(); }
    QList<AbstractBookmarkItem*> children() { return m_children; }
    AbstractBookmarkItem *child(int number) { return m_children.value(number); }
    AbstractBookmarkItem *parent() { return m_parent; }

    int childNumber() const
    {
        if (m_parent)
            return m_parent->children().indexOf(const_cast<AbstractBookmarkItem*>(this));

        return 0;
    }

protected:
    ItemType m_type;
    AbstractBookmarkItem *m_parent;
    QList<AbstractBookmarkItem*> m_children;
    QString m_value;
    QString m_display;
};

class BookmarkRootItem : public AbstractBookmarkItem
{
public:
    BookmarkRootItem()
        : AbstractBookmarkItem()
    {
        m_type = AbstractBookmarkItem::Root;
        m_value = m_display = "root";
    }
};

class BookmarkCommandItem : public AbstractBookmarkItem
{
public:
    BookmarkCommandItem(const QString &name, const QString &command, AbstractBookmarkItem *parent)
        : AbstractBookmarkItem(parent)
    {
        m_type = AbstractBookmarkItem::Command;
        m_value = command;
        m_display = name;
    }
};


class BookmarkGroupItem : public AbstractBookmarkItem
{
public:
    BookmarkGroupItem(const QString &name, AbstractBookmarkItem *parent)
        : AbstractBookmarkItem(parent)
    {
        m_type = AbstractBookmarkItem::Group;
        m_display = name;
    }
};

class BookmarkLocalGroupItem : public BookmarkGroupItem
{
public:
    BookmarkLocalGroupItem(AbstractBookmarkItem *parent)
        : BookmarkGroupItem(QObject::tr("Local Bookmarks"), parent)
    {
        QList<QStandardPaths::StandardLocation> locations;
        locations << QStandardPaths::DesktopLocation
                  << QStandardPaths::DocumentsLocation
                  << QStandardPaths::TempLocation
                  << QStandardPaths::HomeLocation
                  << QStandardPaths::MusicLocation
                  << QStandardPaths::PicturesLocation;

        QString path;
        QString name;
        QString cmd;
        QDir d;

        // standard $HOME subdirs
        foreach (QStandardPaths::StandardLocation i, locations)
        {
            path = QStandardPaths::writableLocation(i);
            if (!d.exists(path))
            {
                continue;
            }
            name = QStandardPaths::displayName(i);

            path.replace(" ", "\\ ");
            cmd = "cd " + path;

            addChild(new BookmarkCommandItem(name, cmd, this));
        }

        // system env - include dirs in the tree
        QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
        foreach (const QString &i, env.keys())
        {
            path = env.value(i);
            if (!d.exists(path) || !QFileInfo(path).isDir())
            {
                continue;
            }
            path.replace(" ", "\\ ");
            cmd = "cd " + path;
            addChild(new BookmarkCommandItem(i, cmd, this));
        }
    }
};

class BookmarkFileGroupItem : public BookmarkGroupItem
{
    // hierarchy handling
    // m_pos to group map. Example: group1.group2=item
    QHash<QString,AbstractBookmarkItem*> m_map;
    // current position level. Example "group1", "group2"
    QStringList m_pos;

public:
    BookmarkFileGroupItem(AbstractBookmarkItem *parent, const QString &fname)
        : BookmarkGroupItem(QObject::tr("Synchronized Bookmarks"), parent)
    {
        QFile f(fname);
        if (!f.open(QIODevice::ReadOnly))
        {
            qDebug() << "Canot open file" << fname;
            // TODO/FIXME: message box
            return;
        }

        QXmlStreamReader xml;
        xml.setDevice(&f);

        while (true)
        {
            xml.readNext();

            switch (xml.tokenType())
            {
            case QXmlStreamReader::StartElement:
            {
                AbstractBookmarkItem *parent = m_map.contains(xmlPos()) ? m_map[xmlPos()] : this;
                QString tag = xml.name().toString();
                if (tag == "group")
                {
                    QString name = xml.attributes().value("name").toString();
                    m_pos.append(name);

                    BookmarkGroupItem *i = new BookmarkGroupItem(name, parent);
                    parent->addChild(i);

                    m_map[xmlPos()] = i;
                }
                else if (tag == "command")
                {
                    QString name = xml.attributes().value("name").toString();
                    QString cmd = xml.attributes().value("value").toString();

                    BookmarkCommandItem *i = new BookmarkCommandItem(name, cmd, parent);
                    parent->addChild(i);
                }
                break;
            }
            case QXmlStreamReader::EndElement:
            {
                QString tag = xml.name().toString();
                if (tag == "group")
                {
                    m_pos.removeLast();
                }
                break;
            }
            case QXmlStreamReader::Invalid:
                qDebug() << "XML error: " << xml.errorString().constData()
                         << xml.lineNumber() << xml.columnNumber();
                m_map.clear();
                return;
            case QXmlStreamReader::EndDocument:
                m_map.clear();
                return;
            default:
                break;
            } // switch
        } // while
    } // constructor

    QString xmlPos()
    {
        return m_pos.join(".");
    }
};


BookmarksModel::BookmarksModel(QObject *parent)
    : QAbstractItemModel(parent),
      m_root(0)
{
    setup();
}

void BookmarksModel::setup()
{
    if (m_root)
        delete m_root;
    m_root = new BookmarkRootItem();
    m_root->addChild(new BookmarkLocalGroupItem(m_root));
    m_root->addChild(new BookmarkFileGroupItem(m_root, Properties::Instance()->bookmarksFile));
    beginResetModel();
    endResetModel();
}

BookmarksModel::~BookmarksModel()
{
    if (m_root)
        delete m_root;
}

int BookmarksModel::columnCount(const QModelIndex & /* parent */) const
{
    return 2;
}

QVariant BookmarksModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    switch (role)
    {
    case Qt::DisplayRole:
    case Qt::EditRole:
        return index.column() == 0 ? getItem(index)->display() : getItem(index)->value();
    case Qt::FontRole:
    {
        QFont f;
        if (static_cast<AbstractBookmarkItem*>(index.internalPointer())->type() == AbstractBookmarkItem::Group)
        {
            f.setBold(true);
        }
        return f;
    }
    default:
        return QVariant();
    }
}

AbstractBookmarkItem *BookmarksModel::getItem(const QModelIndex &index) const
{
    if (index.isValid())
    {
        AbstractBookmarkItem *item = static_cast<AbstractBookmarkItem*>(index.internalPointer());
        if (item)
            return item;
    }
    return m_root;
 }

QVariant BookmarksModel::headerData(int section, Qt::Orientation orientation,
                                    int role) const
{
    return QVariant();
}

QModelIndex BookmarksModel::index(int row, int column, const QModelIndex &parent) const
{
    if (parent.isValid() && parent.column() != 0)
        return QModelIndex();

    AbstractBookmarkItem *parentItem = getItem(parent);

    AbstractBookmarkItem *childItem = parentItem->child(row);
    if (childItem)
        return createIndex(row, column, childItem);
    else
        return QModelIndex();
}


QModelIndex BookmarksModel::parent(const QModelIndex &index) const
{
    if (!index.isValid())
        return QModelIndex();

    AbstractBookmarkItem *childItem = getItem(index);
    AbstractBookmarkItem *parentItem = childItem->parent();

    if (parentItem == m_root)
        return QModelIndex();

    return createIndex(parentItem->childNumber(), 0, parentItem);
}

int BookmarksModel::rowCount(const QModelIndex &parent) const
{
    AbstractBookmarkItem *parentItem = getItem(parent);
    return parentItem->childCount();
}

#if 0
bool BookmarksModel::setData(const QModelIndex &index, const QVariant &value,
                             int role)
{
    if (role != Qt::EditRole)
        return false;

    AbstractBookmarkItem *item = getItem(index);
    bool result = item->setData(index.column(), value);

    if (result)
        emit dataChanged(index, index);

    return result;
}
#endif


BookmarksWidget::BookmarksWidget(QWidget *parent)
    : QWidget(parent)
{
    setupUi(this);

    m_model = new BookmarksModel(this);
    treeView->setModel(m_model);
    treeView->header()->hide();

    connect(treeView, SIGNAL(doubleClicked(QModelIndex)),
            this, SLOT(handleCommand(QModelIndex)));
}

BookmarksWidget::~BookmarksWidget()
{
}

void BookmarksWidget::setup()
{
    m_model->setup();

    treeView->expandAll();
    treeView->resizeColumnToContents(0);
    treeView->resizeColumnToContents(1);
}

void BookmarksWidget::handleCommand(const QModelIndex& index)
{
    AbstractBookmarkItem *item = static_cast<AbstractBookmarkItem*>(index.internalPointer());
    if (!item || item->type() != AbstractBookmarkItem::Command)
        return;

    emit callCommand(item->value() + "\n"); // TODO/FIXME: decide how to handle EOL
}
