/*
 * Copyright (C) 2005-2022 Centre National d'Etudes Spatiales (CNES)
 *
 * This file is part of Orfeo Toolbox
 *
 *     https://www.orfeo-toolbox.org/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef mvdStackedLayerModel_h
#define mvdStackedLayerModel_h

//
// Configuration include.
//// Included at first position before any other ones.
#include "ConfigureMonteverdi.h"

#include "OTBMonteverdiCoreExport.h"

/*****************************************************************************/
/* INCLUDE SECTION                                                           */

//
// Qt includes (sorted by alphabetic order)
//// Must be included before system/custom includes.
#include <QtCore>

//
// System includes (sorted by alphabetic order)

//
// ITK includes (sorted by alphabetic order)

//
// OTB includes (sorted by alphabetic order)

//
// Monteverdi includes (sorted by alphabetic order)
#include "mvdAbstractModel.h"
#include "mvdTypes.h"

/*****************************************************************************/
/* PRE-DECLARATION SECTION                                                   */

//
// External classes pre-declaration.
namespace
{
}

namespace mvd
{
//
// Internal classes pre-declaration.
class AbstractLayerModel;

/*****************************************************************************/
/* CLASS DEFINITION SECTION                                                  */

/**
 * \class StackedLayerModel
 *
 * \ingroup OTBMonteverdiCore
 *
 * \brief WIP.
 */
class OTBMonteverdiCore_EXPORT StackedLayerModel : public AbstractModel
{

  /*-[ QOBJECT SECTION ]-----------------------------------------------------*/

  Q_OBJECT;

  //
  // Private types
private:
  // typedef std::vector< AbstractLayerModel * > LayerModelVector;
  typedef std::map<std::string, AbstractLayerModel*> LayerModelMap;

  // typedef std::pair< AbstractLayerModel *, void * > LayerModelPair;
  // typedef std::map< std::string, LayerDataPair > LayerModelPairMap;

  /*-[ PUBLIC SECTION ]------------------------------------------------------*/

  //
  // Public types.
public:
  typedef std::vector<LayerModelMap::key_type> KeyVector;

  typedef LayerModelMap::size_type      SizeType;
  typedef LayerModelMap::key_type       KeyType;
  typedef LayerModelMap::const_iterator ConstIterator;
  static const SizeType                 NIL_INDEX;

  //
  // Public methods.
public:
  /** \brief Constructor. */
  StackedLayerModel(QObject* p = NULL);

  /** \brief Destructor. */
  ~StackedLayerModel() override;

  inline const AbstractLayerModel* operator[](SizeType) const;
  inline AbstractLayerModel* operator[](SizeType);

  KeyType Add(AbstractLayerModel*);

  inline const AbstractLayerModel* At(SizeType) const;
  inline AbstractLayerModel*       At(SizeType);

  inline ConstIterator Begin() const;

  inline void BeginEditPixelInfo(){};

  void BeginEditResolutions(){};

  inline bool Contains(const KeyType& key) const;
  bool Contains(const AbstractLayerModel*) const;

  void CountSRT(size_t& unknown, size_t& carto, size_t& geo, size_t& sensor) const;

  inline ConstIterator End() const;

  inline void EndEditPixelInfo(const QPoint&, const PointType&);

  void EndEditResolutions();

  /*
  inline const AbstractLayerModel * Front() const;
  inline AbstractLayerModel * Front();
  */
  inline ConstIterator Find(const AbstractLayerModel*) const;

  inline const AbstractLayerModel* Get(const KeyType&) const;
  inline AbstractLayerModel*       Get(const KeyType&);

  inline SizeType GetCount() const;

  inline const KeyType& GetKey(SizeType) const;

  inline const KeyVector& GetKeys() const;

  template <typename T>
  T* GetCurrent() const;

  template <typename T>
  T* GetCurrent();

  AbstractLayerModel* GetCurrent() const;
  AbstractLayerModel* GetCurrent();

  inline SizeType GetCurrentIndex() const;

  inline const KeyType& GetCurrentKey() const;

  template <typename T>
  T* GetReference() const;

  template <typename T>
  T* GetReference();

  AbstractLayerModel* GetReference() const;
  AbstractLayerModel* GetReference();

  inline SizeType GetReferenceIndex() const;

  inline bool HasCurrent() const;

  inline bool HasReference() const;

  inline SizeType IndexOf(const AbstractLayerModel*) const;

  KeyType Insert(AbstractLayerModel*, SizeType index);

  inline bool IsEmpty() const;

  inline const PixelInfo::Vector& PixelInfos() const;
  inline PixelInfo::Vector&       PixelInfos();

  inline void SetCurrent(SizeType, bool = false);
  void        SetCurrent(const KeyType&);
  void        SetCurrent(const AbstractLayerModel*);

  inline void SetReference(SizeType, bool = false);
  void        SetReference(const KeyType&);
  void        SetReference(const AbstractLayerModel*);

  /*-[ PUBLIC SLOTS SECTION ]------------------------------------------------*/

  //
  // Public SLOTS.
public Q_SLOTS:
  void Clear();
  void Deleting(unsigned int index);
  inline void Delete(const KeyType&);
  inline void DeleteCurrent();
  inline void LowerCurrent();
  inline void MoveCurrentToBottom();
  inline void MoveCurrentToTop();
  inline void RaiseCurrent();
  inline void RotateLayers(int);
  inline void SelectFirst();
  inline void SelectLast();
  inline void SelectPrevious();
  inline void SelectNext();

  /*-[ SIGNALS SECTION ]-----------------------------------------------------*/

  //
  // Signals.
Q_SIGNALS:
  void AboutToChangeSelectedLayerModel(const StackedLayerModel::KeyType&);
  void SelectedLayerModelChanged(const StackedLayerModel::KeyType&);

  void ContentAboutToBeChanged();
  void ContentAboutToBeReset();
  void ContentChanged();
  void ContentReset();

  void CurrentAboutToBeChanged(size_t);
  void CurrentChanged(size_t);

  void LayerAboutToBeDeleted(size_t index);
  void LayerAdded(size_t index);
  void LayerDeleted(size_t index);

  void OrderAboutToBeChanged();
  void OrderChanged();

  void PixelInfoChanged(const QPoint&, const PointType&, const PixelInfo::Vector&);

  void ReferenceAboutToBeChanged(size_t);
  void ReferenceChanged(size_t);

  void ResolutionsChanged(const PixelInfo::Vector&);

  void LayerRenamed();

  /*-[ PROTECTED SECTION ]---------------------------------------------------*/

  //
  // Protected methods.
protected:
  //
  // Protected attributes.
protected:
  /*-[ PRIVATE SECTION ]-----------------------------------------------------*/

  //
  // Private methods.
private:
  static KeyType GenerateKey(AbstractLayerModel*);

  inline void ClearPixelInfos();

  void Delete(SizeType);

  inline SizeType FindKey(const KeyType&) const;

  inline SizeType Next(SizeType);
  inline SizeType Prev(SizeType);

  void RaiseLayer(SizeType);
  void LowerLayer(SizeType);

  void RotateLayerUp(SizeType);
  void RotateLayerDown(SizeType);

  void MoveTo(SizeType index, SizeType position);
  void MoveToTop(SizeType);
  void MoveToBottom(SizeType);

  // inline void Swap( SizeType, SizeType );

  inline const KeyType& KeyOf(const AbstractLayerModel*) const;

  //
  // Private attributes.
private:
  static const KeyType NIL_KEY;

  static SizeType m_LayerCount;

  LayerModelMap m_LayerModels;
  KeyVector     m_Keys;
  SizeType      m_Current;
  SizeType      m_Reference;

  // Usually, information is a singular term which denotes a
  // plural. Since there's a container of several pixel-infos and we
  // usually name container variables by using the plural form, an 's'
  // is appended to PixelInfo.
  PixelInfo::Vector m_PixelInfos;

  /*-[ PRIVATE SLOTS SECTION ]-----------------------------------------------*/

  //
  // Slots.
private Q_SLOTS:
};

#if OTB_DEBUG
inline void trace(const StringVector&);
#endif

} // end namespace 'mvd'.

/*****************************************************************************/
/* INLINE SECTION                                                            */

//
// Qt includes (sorted by alphabetic order)
//// Must be included before system/custom includes.

//
// System includes (sorted by alphabetic order)

//
// ITK includes (sorted by alphabetic order)

//
// OTB includes (sorted by alphabetic order)

//
// Monteverdi includes (sorted by alphabetic order)

namespace mvd
{

/*****************************************************************************/
inline const AbstractLayerModel* StackedLayerModel::operator[](SizeType i) const
{
  return At(i);
}

/*****************************************************************************/
inline AbstractLayerModel* StackedLayerModel::operator[](SizeType i)
{
  return At(i);
}

/*****************************************************************************/
inline const AbstractLayerModel* StackedLayerModel::At(SizeType i) const
{
  return const_cast<StackedLayerModel*>(this)->At(i);
}

/*****************************************************************************/
inline AbstractLayerModel* StackedLayerModel::At(SizeType i)
{
  if (i >= GetCount())
    return NULL;

  assert(!GetKey(i).empty());

  LayerModelMap::const_iterator it(m_LayerModels.find(GetKey(i)));

  assert(it != m_LayerModels.end());

  return it->second;
}

/*****************************************************************************/
inline StackedLayerModel::ConstIterator StackedLayerModel::Begin() const
{
  return m_LayerModels.begin();
}

/*****************************************************************************/
inline void StackedLayerModel::ClearPixelInfos()
{
  m_PixelInfos.reserve(0);
}

/*****************************************************************************/
inline bool StackedLayerModel::Contains(const KeyType& key) const
{
  return m_LayerModels.find(key) != m_LayerModels.end();
}

/*****************************************************************************/
inline void StackedLayerModel::Delete(const KeyType& key)
{
  // qDebug() << this << "::Delete(" << key << ")";

  Delete(FindKey(key));
}

/*****************************************************************************/
inline void StackedLayerModel::DeleteCurrent()
{
  // qDebug() << this << "::DeleteCurrent()";

  Delete(m_Current);
}

/*****************************************************************************/
inline StackedLayerModel::SizeType StackedLayerModel::FindKey(const KeyType& key) const
{
  KeyVector::const_iterator it(std::find(m_Keys.begin(), m_Keys.end(), key));

  if (it == m_Keys.end())
    return StackedLayerModel::NIL_INDEX;

  return std::distance(m_Keys.begin(), it);
}

/*****************************************************************************/
inline StackedLayerModel::SizeType StackedLayerModel::GetCount() const
{
  return m_LayerModels.size();
}

/*****************************************************************************/
inline const StackedLayerModel::KeyVector& StackedLayerModel::GetKeys() const
{
  return m_Keys;
}

/*****************************************************************************/
inline bool StackedLayerModel::HasCurrent() const
{
  return m_Current < GetCount();
}

/*****************************************************************************/
inline bool StackedLayerModel::HasReference() const
{
  return m_Reference < GetCount();
}

/*****************************************************************************/
inline StackedLayerModel::ConstIterator StackedLayerModel::End() const
{
  return m_LayerModels.end();
}

/*****************************************************************************/
inline void StackedLayerModel::EndEditPixelInfo(const QPoint& screen, const PointType& view)
{
  Q_EMIT PixelInfoChanged(screen, view, m_PixelInfos);
}

/*****************************************************************************/
StackedLayerModel::ConstIterator StackedLayerModel::Find(const AbstractLayerModel* layer) const
{
  for (LayerModelMap::const_iterator it(m_LayerModels.begin()); it != m_LayerModels.end(); ++it)
    if (it->second == layer)
      return it;

  return m_LayerModels.end();
}

/*****************************************************************************/
const AbstractLayerModel* StackedLayerModel::Get(const KeyType& key) const
{
  return const_cast<StackedLayerModel*>(this)->Get(key);
}

/*****************************************************************************/
AbstractLayerModel* StackedLayerModel::Get(const KeyType& key)
{
  ConstIterator it(m_LayerModels.find(key));

  return it == m_LayerModels.end() ? NULL : it->second;
}

/*****************************************************************************/
template <typename T>
inline T* StackedLayerModel::GetCurrent()
{
  return dynamic_cast<T*>(GetCurrent());
}

/*****************************************************************************/
template <typename T>
inline T* StackedLayerModel::GetCurrent() const
{
  return dynamic_cast<T*>(GetCurrent());
}

/*****************************************************************************/
inline AbstractLayerModel* StackedLayerModel::GetCurrent()
{
  if (m_Current >= GetCount())
    return NULL;

  LayerModelMap::const_iterator it(m_LayerModels.find(m_Keys[m_Current]));

  if (it == m_LayerModels.end())
    return NULL;

  return it->second;
}

/*****************************************************************************/
inline AbstractLayerModel* StackedLayerModel::GetCurrent() const
{
  return const_cast<StackedLayerModel*>(this)->GetCurrent();
}

/*****************************************************************************/
inline StackedLayerModel::SizeType StackedLayerModel::GetCurrentIndex() const
{
  return m_Current;
}

/*****************************************************************************/
inline const StackedLayerModel::KeyType& StackedLayerModel::GetCurrentKey() const
{
  return GetKey(m_Current);
}

/*****************************************************************************/
inline const StackedLayerModel::KeyType& StackedLayerModel::GetKey(SizeType index) const
{
  if (index >= GetCount())
    return StackedLayerModel::NIL_KEY;

  return m_Keys[index];
}

/*****************************************************************************/
template <typename T>
inline T* StackedLayerModel::GetReference()
{
  return dynamic_cast<T*>(GetReference());
}

/*****************************************************************************/
template <typename T>
inline T* StackedLayerModel::GetReference() const
{
  return dynamic_cast<T*>(GetReference());
}

/*****************************************************************************/
inline AbstractLayerModel* StackedLayerModel::GetReference()
{
  if (m_Reference >= GetCount())
    return NULL;

  LayerModelMap::const_iterator it(m_LayerModels.find(m_Keys[m_Reference]));

  if (it == m_LayerModels.end())
    return NULL;

  return it->second;
}

/*****************************************************************************/
inline AbstractLayerModel* StackedLayerModel::GetReference() const
{
  return const_cast<StackedLayerModel*>(this)->GetReference();
}

/*****************************************************************************/
inline StackedLayerModel::SizeType StackedLayerModel::GetReferenceIndex() const
{
  return m_Reference;
}


/*****************************************************************************/
inline StackedLayerModel::SizeType StackedLayerModel::IndexOf(const AbstractLayerModel* layer) const
{
  KeyType key(KeyOf(layer));

  if (key == StackedLayerModel::NIL_KEY)
    return StackedLayerModel::NIL_INDEX;

  for (KeyVector::size_type i = 0; i < m_Keys.size(); ++i)
    if (m_Keys[i] == key)
      return i;

  return StackedLayerModel::NIL_INDEX;
}

/*****************************************************************************/
inline bool StackedLayerModel::IsEmpty() const
{
  return m_LayerModels.empty();
}

/*****************************************************************************/
inline const StackedLayerModel::KeyType& StackedLayerModel::KeyOf(const AbstractLayerModel* layerModel) const
{
  for (LayerModelMap::const_iterator it(m_LayerModels.begin()); it != m_LayerModels.end(); ++it)
    if (it->second == layerModel)
      return it->first;

  return StackedLayerModel::NIL_KEY;
}

/*****************************************************************************/
inline void StackedLayerModel::LowerCurrent()
{
  if (GetCount() < 2)
    return;

  assert(m_Current < GetCount());

  if (m_Current == GetCount() - 1)
    RotateLayerDown(1);
  else
    LowerLayer(m_Current);
}

/*****************************************************************************/
void StackedLayerModel::MoveCurrentToBottom()
{
  if (m_Current >= GetCount())
    return;

  MoveToBottom(m_Current);
}

/*****************************************************************************/
void StackedLayerModel::MoveCurrentToTop()
{
  if (m_Current >= GetCount())
    return;

  MoveToTop(m_Current);
}

/*****************************************************************************/
inline StackedLayerModel::SizeType StackedLayerModel::Next(SizeType index)
{
  return index >= GetCount() ? 0 : (index + 1) % GetCount();
}

/*****************************************************************************/
inline const PixelInfo::Vector& StackedLayerModel::PixelInfos() const
{
  return m_PixelInfos;
}

/*****************************************************************************/
inline PixelInfo::Vector& StackedLayerModel::PixelInfos()
{
  return m_PixelInfos;
}

/*****************************************************************************/
inline StackedLayerModel::SizeType StackedLayerModel::Prev(SizeType index)
{
  return index >= GetCount() || index == 0 ? GetCount() - 1 : index - 1;
}

/*****************************************************************************/
inline void StackedLayerModel::RaiseCurrent()
{
  if (GetCount() < 2)
    return;

  assert(m_Current < GetCount());

  if (m_Current == 0)
    RotateLayerUp(1);
  else
    RaiseLayer(m_Current);
}

/*****************************************************************************/
inline void StackedLayerModel::RotateLayers(int steps)
{
  if (IsEmpty())
    return;

  if (steps >= 0)
    RotateLayerUp(steps % GetCount());

  else
    RotateLayerDown((-steps) % GetCount());
}

/*****************************************************************************/
inline void StackedLayerModel::SelectFirst()
{
  if (GetCount() < 1)
    return;

  SetCurrent(SizeType(0));
}

/*****************************************************************************/
inline void StackedLayerModel::SelectLast()
{
  if (GetCount() < 1)
    return;

  SetCurrent(m_Keys.size() - 1);
}

/*****************************************************************************/
inline void StackedLayerModel::SelectNext()
{
  if (IsEmpty())
    return;

  SetCurrent(Next(m_Current));
}

/*****************************************************************************/
inline void StackedLayerModel::SelectPrevious()
{
  if (IsEmpty())
    return;

  SetCurrent(Prev(m_Current));
}

/*****************************************************************************/
inline void StackedLayerModel::SetCurrent(SizeType index, bool force)
{
  // qDebug() << this << "::SetCurrent(" << index << ")";

  if (index == m_Current && !force)
    return;

  KeyType key(GetKey(index));

  // qDebug()
  //   << QString( "'%1'" ).arg( GetCurrentKey().c_str() )
  //   << "->"
  //   << QString( "'%1'" ).arg( key.c_str() );

  Q_EMIT CurrentAboutToBeChanged(index);
  Q_EMIT AboutToChangeSelectedLayerModel(key);

  m_Current = index;

  Q_EMIT CurrentChanged(index);
  Q_EMIT SelectedLayerModelChanged(key);
}

/*****************************************************************************/
inline void StackedLayerModel::SetReference(SizeType index, bool force)
{
  // qDebug() << this << "::SetReference(" << index << ")";

  if (index == m_Reference && !force)
    return;

  Q_EMIT ReferenceAboutToBeChanged(index);

  m_Reference = index;

  Q_EMIT ReferenceChanged(index);
}

/*****************************************************************************/
#if OTB_DEBUG

inline void trace(const StringVector& strv)
{
  qDebug() << "{";

  for (StringVector::const_iterator it(strv.begin()); it != strv.end(); ++it)
    qDebug() << QString("%1").arg(it->c_str());

  qDebug() << "}";
}

#endif

} // end namespace 'mvd'

#endif // mvdStackedLayerModel_h
