/***************************************************************************
 *   Copyright (C) 2005 by David Saxton                                    *
 *   david@bluehaze.org                                                    *
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/

#include "itemgroup.h"
#include "icndocument.h"
#include "item.h"
#include "mechanicsdocument.h"
#include "utils.h"

#include <QTimer>
#include <map>

ItemGroup::ItemGroup(ItemDocument *view)
    : QObject(view)
{
    m_activeItem = nullptr;
    b_itemsAreSameType = true;
    p_view = view;
    p_icnDocument = dynamic_cast<ICNDocument *>(p_view);
    p_mechanicsDocument = dynamic_cast<MechanicsDocument *>(p_view);
    QTimer::singleShot(0, this, SLOT(getViewPtrs()));
}

ItemGroup::~ItemGroup()
{
}

void ItemGroup::getViewPtrs()
{
    p_icnDocument = dynamic_cast<ICNDocument *>(p_view);
    p_mechanicsDocument = dynamic_cast<MechanicsDocument *>(p_view);
}

ItemList ItemGroup::items(bool excludeParentedItems) const
{
    if (excludeParentedItems)
        return m_itemList;

    ItemList items = m_itemList;
    ItemList parents = m_itemList;

    int oldSize = items.size();
    do {
        oldSize = items.size();
        ItemList children;

        ItemList::iterator end = parents.end();
        for (ItemList::iterator it = parents.begin(); it != end; ++it)
            children += (*it)->children();

        end = children.end();
        for (ItemList::iterator it = children.begin(); it != end; ++it) {
            if (children.count(*it) > 1)
                *it = nullptr;
        }
        children.removeAll((Item *)nullptr);

        items += children;
        parents = children;
    } while (oldSize != items.size());

    return items;
}

bool ItemGroup::itemsHaveSameDataValue(const QString &id) const
{
    if (m_itemList.size() < 1) {
        return true;
    }

    if (!itemsAreSameType()) {
        return false;
    }

    ItemList::const_iterator it = m_itemList.begin();
    const ItemList::const_iterator end = m_itemList.end();
    QVariant firstData = (*it)->property(id)->value();
    for (++it; it != end; ++it) {
        if ((*it) && (*it)->property(id) && (*it)->property(id)->value() != firstData) {
            return false;
        }
    }
    return true;
}

bool ItemGroup::itemsHaveSameData() const
{
    if (m_itemList.size() < 1) {
        return true;
    }

    if (!itemsAreSameType()) {
        return false;
    }

    VariantDataMap *variantMap = m_itemList.first()->variantMap();
    const VariantDataMap::const_iterator vitEnd = variantMap->end();
    for (VariantDataMap::const_iterator vit = variantMap->begin(); vit != vitEnd; ++vit) {
        if (!itemsHaveSameDataValue(vit.key())) {
            return false;
        }
    }
    return true;
}

bool ItemGroup::itemsHaveDefaultData() const
{
    if (!itemsHaveSameData()) {
        return false;
    }

    if (m_itemList.size() < 1) {
        return true;
    }

    VariantDataMap *variantMap = (*m_itemList.begin())->variantMap();
    const VariantDataMap::const_iterator vitEnd = variantMap->end();
    for (VariantDataMap::const_iterator vit = variantMap->begin(); vit != vitEnd; ++vit) {
        if (!vit.value()->isHidden() && vit.value()->value() != vit.value()->defaultValue())
            return false;
    }
    return true;
}

void ItemGroup::registerItem(Item *item)
{
    if (!item || m_itemList.contains(item)) {
        return;
    }

    m_itemList += item;
    updateAreSameStatus();
}

void ItemGroup::unregisterItem(Item *item)
{
    if (m_itemList.removeAll(item) > 0) {
        updateAreSameStatus();
    }
}

void ItemGroup::updateAreSameStatus()
{
    b_itemsAreSameType = true;

    if (m_itemList.size() < 2) {
        return;
    }

    QString activeId = (*m_itemList.begin())->id();
    int discardIndex = activeId.lastIndexOf("__");
    if (discardIndex != -1)
        activeId.truncate(discardIndex);

    const ItemList::iterator end = m_itemList.end();
    for (ItemList::iterator it = ++m_itemList.begin(); it != end && b_itemsAreSameType; ++it) {
        if (*it) {
            QString id = (*it)->id();
            discardIndex = id.lastIndexOf("__");
            if (discardIndex != -1)
                id.truncate(discardIndex);
            if (id != activeId) {
                b_itemsAreSameType = false;
            }
        }
    }
}

void ItemGroup::slotAlignHorizontally()
{
    if (m_itemList.size() < 2)
        return;

    double avg_y = 0.;

    const ItemList::iterator end = m_itemList.end();
    for (ItemList::iterator it = m_itemList.begin(); it != end; ++it)
        avg_y += (*it)->y();

    int new_y = int(avg_y / (8 * m_itemList.size())) * 8 + 4;

    for (ItemList::iterator it = m_itemList.begin(); it != end; ++it)
        (*it)->move((*it)->x(), new_y);

    p_icnDocument->requestStateSave();
}

void ItemGroup::slotAlignVertically()
{
    if (m_itemList.size() < 2)
        return;

    double avg_x = 0.;

    const ItemList::iterator end = m_itemList.end();
    for (ItemList::iterator it = m_itemList.begin(); it != end; ++it)
        avg_x += (*it)->x();

    int new_x = int(avg_x / (8 * m_itemList.size())) * 8 + 4;

    for (ItemList::iterator it = m_itemList.begin(); it != end; ++it)
        (*it)->move(new_x, (*it)->y());

    p_icnDocument->requestStateSave();
}

void ItemGroup::slotDistributeHorizontally()
{
    if (m_itemList.size() < 2)
        return;

    // We sort the items by their horizontal position so that we can calculate
    // an average spacing
    typedef std::multimap<double, Item *> DIMap;

    DIMap ranked;
    const ItemList::iterator ilend = m_itemList.end();
    for (ItemList::iterator it = m_itemList.begin(); it != ilend; ++it)
        ranked.insert(std::make_pair((*it)->x(), *it));

    double avg_spacing = 0;

    Item *previous = nullptr;
    const DIMap::iterator rankedEnd = ranked.end();
    for (DIMap::iterator it = ranked.begin(); it != rankedEnd; ++it) {
        Item *item = it->second;
        if (previous) {
            double spacing = item->x() + item->offsetX() - (previous->x() + previous->width() + previous->offsetX());
            avg_spacing += spacing;
        }
        previous = item;
    }

    avg_spacing /= (m_itemList.size() - 1);

    DIMap::iterator it = ranked.begin();
    // Position that we are up to
    double at = it->second->x() + it->second->width() + it->second->offsetX();
    for (++it; it != rankedEnd; ++it) {
        Item *item = it->second;
        double new_x = at - item->offsetX() + avg_spacing;
        item->move(snapToCanvas(new_x), item->y());
        at = new_x + item->width() + item->offsetX();
    }

    p_icnDocument->requestStateSave();
}

void ItemGroup::slotDistributeVertically()
{
    if (m_itemList.size() < 2)
        return;

    // We sort the items by their horizontal position so that we can calculate
    // an average spacing
    typedef std::multimap<double, Item *> DIMap;

    DIMap ranked;
    const ItemList::iterator ilend = m_itemList.end();
    for (ItemList::iterator it = m_itemList.begin(); it != ilend; ++it)
        ranked.insert(std::make_pair((*it)->y(), *it));

    double avg_spacing = 0;

    Item *previous = nullptr;
    const DIMap::iterator rankedEnd = ranked.end();
    for (DIMap::iterator it = ranked.begin(); it != rankedEnd; ++it) {
        Item *item = it->second;
        if (previous) {
            double spacing = item->y() + item->offsetY() - (previous->y() + previous->height() + previous->offsetY());
            avg_spacing += spacing;
        }
        previous = item;
    }

    avg_spacing /= (m_itemList.size() - 1);

    DIMap::iterator it = ranked.begin();
    // Position that we are up to
    double at = it->second->y() + it->second->height() + it->second->offsetY();
    for (++it; it != rankedEnd; ++it) {
        Item *item = it->second;
        double new_y = at - item->offsetY() + avg_spacing;
        item->move(item->x(), snapToCanvas(new_y));
        at = new_y + item->height() + item->offsetY();
    }

    p_icnDocument->requestStateSave();
}
