/***************************************************************************
**
**  This file is part of GeopsyGui.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file 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 Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2019-08-30
**  Copyright: 2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "SeismicEventItem.h"

namespace GeopsyGui {

  int SeismicEventItem::_sortColumn=0;
  Qt::SortOrder SeismicEventItem::_sortOrder=Qt::AscendingOrder;

/*!
    \class SeismicEventItem SeismicEventItem.h
    \brief Brief description of class still missing

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  SeismicEventItem::SeismicEventItem(QObject * parent)
    : QAbstractTableModel(parent)
  {
    TRACE;
    _db=nullptr;
  }

  /*!
    Description of destructor still missing
  */
  SeismicEventItem::~SeismicEventItem()
  {
    TRACE;
  }

  void SeismicEventItem::setDatabase(SignalDatabase * db)
  {
    TRACE;
    beginResetModel();
    if(_db!=db) {
      _db=db;
      SeismicEventTable * t=_db->seismicEvents();
      connect(t, SIGNAL(destroyed()), this, SLOT(databaseDestroyed()));
      connect(t, SIGNAL(eventAdded(SeismicEvent *)),
              this, SLOT(add(SeismicEvent *)), Qt::QueuedConnection);
      connect(t, SIGNAL(eventRemoved(SeismicEvent *)),
              this, SLOT(remove(SeismicEvent *)), Qt::QueuedConnection);
    }
    endResetModel();
  }

  void SeismicEventItem::databaseDestroyed()
  {
    TRACE;
    beginResetModel();
    _db=nullptr;
    _list.clear();
    endResetModel();
  }

  int SeismicEventItem::rowCount(const QModelIndex&) const
  {
    TRACE;
    return _list.count();
  }

  /*!
    Currently returns only 1 but may evolve in the future
  */
  int SeismicEventItem::columnCount(const QModelIndex&) const
  {
    TRACE;
    return 9;
  }

  QVariant SeismicEventItem::data(const QModelIndex& index, int role) const
  {
    TRACE;
    if(!index.isValid()) return QVariant();
    const SeismicEvent * e=_list.at(index.row());
    switch (role) {
    case Qt::EditRole:
    case Qt::DisplayRole:
      switch(index.column()) {
      case 0: return e->time().toString(DateTime::defaultUserFormat);
      case 1: return e->utmZone().toString();
      case 2: return e->position().x();
      case 3: return e->position().y();
      case 4: return e->position().z();
      case 5: return SeismicEvent::userType(e->type());
      case 6: return e->force().x();
      case 7: return e->force().y();
      case 8: return e->force().z();
      default: break;
      }
      break;
    case Qt::ToolTipRole:
      if(e->isLocked()) {
        return tr("This event is locked, close all tools to edit");
      }
      break;
    case Qt::DecorationRole: break;
    default: break;
    }
    return QVariant();
  }

  QVariant SeismicEventItem::headerData(int section, Qt::Orientation orientation, int role) const
  {
    switch(role) {
    case Qt::DisplayRole:
      if(orientation==Qt::Vertical) {
        return section+1;
      } else {
        switch(section) {
        case 0: return tr("Time");
        case 1: return tr("UTM zone");
        case 2: return tr("X");
        case 3: return tr("Y");
        case 4: return tr("Z");
        case 5: return tr("Type");
        case 6: return tr("Force X");
        case 7: return tr("Force Y");
        case 8: return tr("Force Z");
        default: break;
        }
        if(section==0) {
          return tr("Pattern");
        } else {
          return tr("Component");
        }
      }
    default:
      return QVariant();
    }
  }

  bool SeismicEventItem::commitModification(SeismicEvent * originalEvent,
                                            const SeismicEvent& newEvent)
  {
    SeismicEventTable * events=_db->seismicEvents();
    if(newEvent.time()==originalEvent->time() &&
       newEvent.isSamePosition(*originalEvent)) {
      *originalEvent=newEvent;
    } else {
      if(events->contains(newEvent)) {
        events->remove(newEvent);
      }
      *originalEvent=newEvent;
      events->sort();
    }
    return true;
  }

  bool SeismicEventItem::setData(const QModelIndex& index, const QVariant& value, int role)
  {
    TRACE;
    if(!index.isValid()) return false;
    SeismicEvent * originalEvent=_list.at(index.row());
    SeismicEvent newEvent(*originalEvent);
    switch (role) {
    case Qt::EditRole:
      switch(index.column()) {
      case 0: {
          DateTime t;
          if(t.fromString(value.toString(), DateTime::defaultUserFormat)) {
            newEvent.setTime(t);
            return commitModification(originalEvent, newEvent);
          }
        }
        break;
      case 1: {
          UtmZone z;
          z.fromString(value.toString());
          newEvent.setUtmZone(z);
          return commitModification(originalEvent, newEvent);
        }
      case 2: {
          bool ok;
          Point p=newEvent.position();
          p.setX(value.toDouble(&ok));
          if(ok) {
            newEvent.setPosition(p);
            return commitModification(originalEvent, newEvent);
          }
        }
        break;
      case 3: {
          bool ok;
          Point p=newEvent.position();
          p.setY(value.toDouble(&ok));
          if(ok) {
            newEvent.setPosition(p);
            return commitModification(originalEvent, newEvent);
          }
        }
        break;
      case 4: {
          bool ok;
          Point p=newEvent.position();
          p.setZ(value.toDouble(&ok));
          if(ok) {
            newEvent.setPosition(p);
            return commitModification(originalEvent, newEvent);
          }
        }
        break;
      case 5: {
          bool ok=true;
          SeismicEvent::Type t=SeismicEvent::userType(value.toString(), &ok);
          if(ok) {
            newEvent.setType(t);
            return commitModification(originalEvent, newEvent);
          }
        }
        break;
      case 6: {
          bool ok;
          Point p=newEvent.force();
          p.setX(value.toDouble(&ok));
          if(ok) {
            newEvent.setForce(p);
            return commitModification(originalEvent, newEvent);
          }
        }
        break;
      case 7: {
          bool ok;
          Point p=newEvent.force();
          p.setY(value.toDouble(&ok));
          if(ok) {
            newEvent.setForce(p);
            return commitModification(originalEvent, newEvent);
          }
        }
        break;
      case 8: {
          bool ok;
          Point p=newEvent.force();
          p.setZ(value.toDouble(&ok));
          if(ok) {
            newEvent.setForce(p);
            return commitModification(originalEvent, newEvent);
          }
        }
        break;
      default: break;
      }
      break;
    default: break;
    }
    return false;
  }

  void SeismicEventItem::add(SeismicEvent * e)
  {
    TRACE;
    int i=_list.count();
    beginInsertRows(QModelIndex(), i, i);
    _list.append(e);
    endInsertRows();

  }

  void SeismicEventItem::remove(SeismicEvent * e)
  {
    TRACE;
    int i=_list.indexOf(e);
    if(i>=0) {
      beginRemoveRows(QModelIndex(), i, i);
      _list.removeAt(i);
      endRemoveRows();
    }
  }

  void SeismicEventItem::sort(int column, Qt::SortOrder order)
  {
    TRACE;
    beginResetModel();
    _sortColumn=column;
    _sortOrder=order;
    std::stable_sort(_list.begin(), _list.end(), sortLessThan);
    endResetModel();
  }

  bool SeismicEventItem::sortLessThan(const SeismicEvent * e1, const SeismicEvent * e2)
  {
    TRACE;
    switch(_sortOrder)
    {
    case Qt::AscendingOrder:
      switch(_sortColumn) {
      case 0:
        return e1->time()<e2->time();
      case 1:
        return e1->utmZone().toString()<e2->utmZone().toString();
      case 2:
        return e1->position().x()<e2->position().x();
      case 3:
        return e1->position().y()<e2->position().y();
      case 4:
        return e1->position().z()<e2->position().z();
      case 5:
        return SeismicEvent::userType(e1->type())<SeismicEvent::userType(e2->type());
      case 6:
        return e1->force().x()<e2->force().x();
      case 7:
        return e1->force().y()<e2->force().y();
      case 8:
        return e1->force().z()<e2->force().z();
      default:
        return true;
      }
    case Qt::DescendingOrder:
      break;
    }
    switch(_sortColumn) {
    case 0:
      return e1->time()>e2->time();
    case 1:
      return e1->utmZone().toString()>e2->utmZone().toString();
    case 2:
      return e1->position().x()>e2->position().x();
    case 3:
      return e1->position().y()>e2->position().y();
    case 4:
      return e1->position().z()>e2->position().z();
    case 5:
      return SeismicEvent::userType(e1->type())>SeismicEvent::userType(e2->type());
    case 6:
      return e1->force().x()>e2->force().x();
    case 7:
      return e1->force().y()>e2->force().y();
    case 8:
      return e1->force().z()>e2->force().z();
    default:
      return true;
    }
  }

  Qt::ItemFlags SeismicEventItem::flags (const QModelIndex & index) const
  {
    const SeismicEvent * e=_list.at(index.row());
    if(e->isLocked()) {
      return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
    } else {
      return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable;
    }
  }

} // namespace GeopsyGui

