/*
 * This file Copyright (C) 2009-2015 Mnemosyne LLC
 *
 * It may be used under the GNU GPL versions 2 or 3
 * or any future license endorsed by Mnemosyne LLC.
 *
 * $Id: FileTreeItem.cc 14609 2015-12-06 17:39:18Z jordan $
 */

#include <algorithm>
#include <cassert>

#include <QApplication>
#include <QStyle>

#include <libtransmission/transmission.h> // priorities

#include "FileTreeItem.h"
#include "FileTreeModel.h"
#include "Formatter.h"
#include "Utils.h" // mime icons

const QHash<QString,int>&
FileTreeItem::getMyChildRows ()
{
  const size_t n = childCount();

  // ensure that all the rows are hashed
  while (myFirstUnhashedRow < n)
    {
      myChildRows.insert (myChildren[myFirstUnhashedRow]->name(),
                          myFirstUnhashedRow);
      ++myFirstUnhashedRow;
    }

  return myChildRows;
}


FileTreeItem::~FileTreeItem ()
{
  assert(myChildren.isEmpty());

  if (myParent != 0)
    {
      const int pos = row();
      assert ((pos>=0) && "couldn't find child in parent's lookup");
      myParent->myChildren.removeAt(pos);
      myParent->myChildRows.remove(name());
      myParent->myFirstUnhashedRow = pos;
    }
}

void
FileTreeItem::appendChild (FileTreeItem * child)
{
  const size_t n = childCount();
  child->myParent = this;
  myChildren.append (child);
  myFirstUnhashedRow = n;
}

FileTreeItem *
FileTreeItem::child (const QString& filename)
{
  FileTreeItem * item(0);

  const int row = getMyChildRows().value (filename, -1);
  if (row != -1)
    {
      item = child (row);
      assert (filename == item->name());
    }

  return item;
}

int
FileTreeItem::row () const
{
  int i(-1);

  if(myParent)
    {
      i = myParent->getMyChildRows().value (name(), -1);
      assert (this == myParent->myChildren[i]);
    }

  return i;
}

QVariant
FileTreeItem::data (int column, int role) const
{
  QVariant value;

  switch (role)
    {
      case FileTreeModel::FileIndexRole:
        value.setValue (myFileIndex);
        break;

      case FileTreeModel::WantedRole:
        value.setValue (isSubtreeWanted ());
        break;

      case FileTreeModel::CompleteRole:
        value.setValue (isComplete ());
        break;

      case Qt::EditRole:
        if (column == FileTreeModel::COL_NAME)
          value.setValue (name ());
        break;

      case Qt::TextAlignmentRole:
        if (column == FileTreeModel::COL_SIZE)
          value = Qt::AlignRight + Qt::AlignVCenter;
        break;

      case Qt::DisplayRole:
      case FileTreeModel::SortRole:
        switch (column)
          {
            case FileTreeModel::COL_NAME:
              value.setValue (name ());
              break;

            case FileTreeModel::COL_SIZE:
              if (role == Qt::DisplayRole)
                value.setValue (sizeString ());
              else
                value.setValue<quint64> (size ());
              break;

            case FileTreeModel::COL_PROGRESS:
              value.setValue (progress ());
              break;

            case FileTreeModel::COL_WANTED:
              value.setValue (isSubtreeWanted ());
              break;

            case FileTreeModel::COL_PRIORITY:
              if (role == Qt::DisplayRole)
                value.setValue (priorityString ());
              else
                value.setValue (priority ());
              break;
          }
        break;

      case Qt::DecorationRole:
        if (column == FileTreeModel::COL_NAME)
          {
            if (myFileIndex < 0)
              value = qApp->style ()->standardIcon (QStyle::SP_DirOpenIcon);
            else
              value = Utils::guessMimeIcon (name ());
          }
        break;
    }

  return value;
}

void
FileTreeItem::getSubtreeWantedSize (uint64_t& have, uint64_t& total) const
{
  if (myIsWanted)
    {
      have += myHaveSize;
      total += myTotalSize;
    }

  for (const FileTreeItem * const i: myChildren)
    i->getSubtreeWantedSize(have, total);
}

double
FileTreeItem::progress () const
{
  double d(0);
  uint64_t have(0), total(0);

  getSubtreeWantedSize (have, total);
  if (total)
    d = have / double(total);

  return d;
}

QString
FileTreeItem::sizeString () const
{
  return Formatter::sizeToString (size ());
}

uint64_t
FileTreeItem::size () const
{
  if (myChildren.isEmpty())
    return myTotalSize;

  uint64_t have = 0;
  uint64_t total = 0;
  getSubtreeWantedSize (have, total);
  return total;
}

std::pair<int,int>
FileTreeItem::update (const QString& name,
                      bool           wanted,
                      int            priority,
                      uint64_t       haveSize,
                      bool           updateFields)
{
  int changed_count = 0;
  int changed_columns[4];

  if (myName != name)
    {
      if (myParent)
        myParent->myFirstUnhashedRow = row();

      myName = name;
      changed_columns[changed_count++] = FileTreeModel::COL_NAME;
    }

  if (fileIndex () != -1)
    {
      if (myHaveSize != haveSize)
        {
          myHaveSize = haveSize;
          changed_columns[changed_count++] = FileTreeModel::COL_PROGRESS;
        }

      if (updateFields)
        {
          if (myIsWanted != wanted)
            {
              myIsWanted = wanted;
              changed_columns[changed_count++] = FileTreeModel::COL_WANTED;
            }

          if (myPriority != priority)
            {
              myPriority = priority;
              changed_columns[changed_count++] = FileTreeModel::COL_PRIORITY;
            }
        }
    }

  std::pair<int,int> changed (-1, -1);
  if (changed_count > 0)
    {
      std::sort (changed_columns, changed_columns+changed_count);
      changed.first = changed_columns[0];
      changed.second = changed_columns[changed_count-1];
    }
  return changed;
}

QString
FileTreeItem::priorityString () const
{
  const int i = priority();

  switch (i)
    {
      case LOW:    return tr("Low");
      case HIGH:   return tr("High");
      case NORMAL: return tr("Normal");
      default:     return tr("Mixed");
    }
}

int
FileTreeItem::priority () const
{
  int i(0);

  if (myChildren.isEmpty())
    {
      switch (myPriority)
        {
          case TR_PRI_LOW:
            i |= LOW;
            break;

          case TR_PRI_HIGH:
            i |= HIGH;
            break;

          default:
            i |= NORMAL;
            break;
        }
    }

  for (const FileTreeItem * const child: myChildren)
    i |= child->priority();

  return i;
}

void
FileTreeItem::setSubtreePriority (int i, QSet<int>& ids)
{
  if (myPriority != i)
    {
      myPriority = i;

      if (myFileIndex >= 0)
        ids.insert (myFileIndex);
    }

  for (FileTreeItem * const child: myChildren)
    child->setSubtreePriority (i, ids);
}

int
FileTreeItem::isSubtreeWanted () const
{
  if(myChildren.isEmpty())
    return myIsWanted ? Qt::Checked : Qt::Unchecked;

  int wanted(-1);
  for (const FileTreeItem * const child: myChildren)
    {
      const int childWanted = child->isSubtreeWanted();

      if (wanted == -1)
        wanted = childWanted;

      if (wanted != childWanted)
        wanted = Qt::PartiallyChecked;

      if (wanted == Qt::PartiallyChecked)
        return wanted;
    }

  return wanted;
}

void
FileTreeItem::setSubtreeWanted (bool b, QSet<int>& ids)
{
  if (myIsWanted != b)
    {
      myIsWanted = b;

      if (myFileIndex >= 0)
        ids.insert(myFileIndex);
    }

  for (FileTreeItem * const child: myChildren)
    child->setSubtreeWanted (b, ids);
}

QString
FileTreeItem::path () const
{
  QString itemPath;
  const FileTreeItem * item = this;

  while (item != NULL && !item->name().isEmpty())
    {
      if (itemPath.isEmpty())
        itemPath = item->name();
      else
        itemPath = item->name() + QLatin1Char ('/') + itemPath;
      item = item->parent ();
    }

  return itemPath;
}

bool
FileTreeItem::isComplete () const
{
  return myHaveSize == totalSize ();
}
