/****************************************************************************
**
** https://www.qxorm.com/
** Copyright (C) 2013 Lionel Marty (contact@qxorm.com)
**
** This file is part of the QxOrm library
**
** This software is provided 'as-is', without any express or implied
** warranty. In no event will the authors be held liable for any
** damages arising from the use of this software
**
** Commercial Usage
** Licensees holding valid commercial QxOrm licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Lionel Marty
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file 'license.gpl3.txt' included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met : http://www.gnu.org/copyleft/gpl.html
**
** If you are unsure which license is appropriate for your use, or
** if you have questions regarding the use of this file, please contact :
** contact@qxorm.com
**
****************************************************************************/

namespace qx {

template <typename Key, typename Value>
QxCollection<Key, Value>::QxCollection() : IxCollection(), m_batch(false)
{
}

template <typename Key, typename Value>
QxCollection<Key, Value>::QxCollection(const QxCollection<Key, Value> & other) : IxCollection(), m_batch(false)
{
   cloneCollection(this, other);
}

template <typename Key, typename Value>
QxCollection<Key, Value>::~QxCollection()
{
}

template <typename Key, typename Value>
QxCollection<Key, Value> & QxCollection<Key, Value>::operator= (const QxCollection<Key, Value> & other)
{
   if (this != (& other)) { cloneCollection(this, other); }
   return (* this);
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::operator== (const QxCollection<Key, Value> & other) const
{
   return isSameCollection(this, other);
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::operator!= (const QxCollection<Key, Value> & other) const
{
   return (! isSameCollection(this, other));
}

template <typename Key, typename Value>
void QxCollection<Key, Value>::cloneCollection(QxCollection<Key, Value> * pClone, const QxCollection<Key, Value> & pRef)
{
   if (! pClone) { return; }
   if (pClone == (& pRef)) { return; }
   QMutexLocker locker1(& pRef.m_mutex);
   QMutexLocker locker2(& pClone->m_mutex);
   qAssert(pRef.m_list.size() == pRef.m_hash.size());
   pClone->m_list = pRef.m_list;
   pClone->m_hash = pRef.m_hash;
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::isSameCollection(const QxCollection<Key, Value> * p1, const QxCollection<Key, Value> & p2) const
{
   if (! p1) { return false; }
   if (p1 == (& p2)) { return true; }
   if (p1->size() != p2.size()) { return false; }
   QMutexLocker locker1(& p2.m_mutex);
   QMutexLocker locker2(& p1->m_mutex);
   qAssert(p2.m_list.size() == p2.m_hash.size());
   return ((p1->m_list == p2.m_list) && (p1->m_hash == p2.m_hash));
}

template <typename Key, typename Value>
void QxCollection<Key, Value>::updateHashPosition(long from /* = 0 */, long to /* = -1 */, bool check /* = false */)
{
   if (m_batch) { return; }
   QMutexLocker locker(& m_mutex);
   qAssert(m_list.size() == m_hash.size());
   if (to == -1) { to = (m_list.size() - 1); }
   if ((from < 0) || (to >= m_list.size()) || (from > to)) { return; }
   for (long idx = from; idx <= to; idx++)
   {
      const Key & key = m_list.at(idx).first;
      m_hash.insert(key, idx);
   }
   if (check) { qAssert(m_list.size() == m_hash.size()); }
}

template <typename Key, typename Value>
typename QxCollection<Key, Value>::iterator QxCollection<Key, Value>::begin()
{
   QMutexLocker locker(& m_mutex);
   return m_list.begin();
}

template <typename Key, typename Value>
typename QxCollection<Key, Value>::iterator QxCollection<Key, Value>::end()
{
   QMutexLocker locker(& m_mutex);
   return m_list.end();
}

template <typename Key, typename Value>
typename QxCollection<Key, Value>::const_iterator QxCollection<Key, Value>::begin() const
{
   QMutexLocker locker(& m_mutex);
   return m_list.begin();
}

template <typename Key, typename Value>
typename QxCollection<Key, Value>::const_iterator QxCollection<Key, Value>::end() const
{
   QMutexLocker locker(& m_mutex);
   return m_list.end();
}

#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))

template <typename Key, typename Value>
typename QxCollection<Key, Value>::reverse_iterator QxCollection<Key, Value>::rbegin()
{
   QMutexLocker locker(& m_mutex);
   return m_list.rbegin();
}

template <typename Key, typename Value>
typename QxCollection<Key, Value>::reverse_iterator QxCollection<Key, Value>::rend()
{
   QMutexLocker locker(& m_mutex);
   return m_list.rend();
}

template <typename Key, typename Value>
typename QxCollection<Key, Value>::const_reverse_iterator QxCollection<Key, Value>::rbegin() const
{
   QMutexLocker locker(& m_mutex);
   return m_list.rbegin();
}

template <typename Key, typename Value>
typename QxCollection<Key, Value>::const_reverse_iterator QxCollection<Key, Value>::rend() const
{
   QMutexLocker locker(& m_mutex);
   return m_list.rend();
}

#endif // (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))

template <typename Key, typename Value>
void QxCollection<Key, Value>::reserve(long size)
{
   if (size <= 0) { return; }
   QMutexLocker locker(& m_mutex);
   qAssert(m_list.size() == m_hash.size());
   m_list.reserve(size);
   m_hash.reserve(size);
}

template <typename Key, typename Value>
void QxCollection<Key, Value>::reverse()
{
   {
      QMutexLocker locker(& m_mutex);
      qAssert(m_list.size() == m_hash.size());
      std::reverse(m_list.begin(), m_list.end());
   }
   updateHashPosition();
}

template <typename Key, typename Value>
void QxCollection<Key, Value>::clear()
{
   QMutexLocker locker(& m_mutex);
   m_hash.clear();
   m_list.clear();
}

template <typename Key, typename Value>
long QxCollection<Key, Value>::count() const
{
   QMutexLocker locker(& m_mutex);
   qAssert(m_list.size() == m_hash.size());
   return static_cast<long>(m_list.size());
}

template <typename Key, typename Value>
long QxCollection<Key, Value>::size() const
{
   return this->count();
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::contains(const Key & key) const
{
   QMutexLocker locker(& m_mutex);
   qAssert(m_list.size() == m_hash.size());
   return (m_hash.contains(key));
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::exist(const Key & key) const
{
   return this->contains(key);
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::empty() const
{
   QMutexLocker locker(& m_mutex);
   qAssert(m_list.size() == m_hash.size());
   return m_list.isEmpty();
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::push_back(const Key & key, const Value & value)
{
   return this->insert(key, value);
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::push_front(const Key & key, const Value & value)
{
   return this->insert(0, key, value);
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::insert(const Key & key, const Value & value)
{
   qAssert(! exist(key));
   QMutexLocker locker(& m_mutex);
   qAssert(m_list.size() == m_hash.size());
   m_list.append(qMakePair(key, value));
   m_hash.insert(key, (m_list.size() - 1));
   return true;
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::insert(long index, const Key & key, const Value & value)
{
   qAssert(! exist(key));
   if (index < 0) { index = 0; }
   if (index >= size()) { return this->insert(key, value); }

   {
      QMutexLocker locker(& m_mutex);
      qAssert(m_list.size() == m_hash.size());
      m_list.insert(index, qMakePair(key, value));
      m_hash.insert(key, index);
   }

   updateHashPosition(index);
   return true;
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::insert(const QxCollection<Key, Value> & other)
{
   {
      if (this == (& other)) { return false; }
      QMutexLocker locker1(& m_mutex);
      QMutexLocker locker2(& other.m_mutex);
      m_list.append(other.m_list);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
      m_hash.insert(other.m_hash);
#else // (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
      m_hash.unite(other.m_hash);
#endif // (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
   }

   updateHashPosition(0, -1, true);
   return true;
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::insert(long index, const QxCollection<Key, Value> & other)
{
   if (index < 0) { index = 0; }
   if ((index >= size()) && (index != 0)) { index = (size() - 1); }
   if (this == (& other)) { return false; }
   QMutexLocker locker1(& other.m_mutex);

   {
      QMutexLocker locker2(& m_mutex);
      qAssert(m_list.size() == m_hash.size());
      m_batch = true;
   }

   for (long l = 0; l < other.size(); l++)
   {
      const type_pair_key_value & pair = other.m_list.at(l);
      this->insert((index + l), pair.first, pair.second);
   }

   { QMutexLocker locker3(& m_mutex); m_batch = false; }
   updateHashPosition(index);
   return true;
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::replace(long index, const Key & key, const Value & value)
{
   qAssert(! exist(key));
   QMutexLocker locker(& m_mutex);
   m_hash.remove(m_list.at(index).first);
   m_list.replace(index, qMakePair(key, value));
   m_hash.insert(key, index);
   qAssert(m_list.size() == m_hash.size());
   return true;
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::swap(long index1, long index2)
{
   if (index1 < 0 || index1 >= size()) { return false; }
   if (index2 < 0 || index2 >= size()) { return false; }
   if (index1 == index2) { return true; }

   QMutexLocker locker(& m_mutex);
   const Key & key1 = m_list.at(index1).first;
   const Key & key2 = m_list.at(index2).first;
   m_hash.insert(key1, index2);
   m_hash.insert(key2, index1);
   m_list.swap(index1, index2);
   qAssert(m_list.size() == m_hash.size());
   return true;
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::move(long indexFrom, long indexTo)
{
   return swap(indexFrom, indexTo);
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::removeByKey(const Key & key)
{
   qAssert(exist(key));
   long pos = 0;

   {
      QMutexLocker locker(& m_mutex);
      pos = m_hash.value(key, -1);
      if ((pos < 0) || (pos >= m_list.size())) { return false; }
      qAssert(m_list.at(pos).first == key);
      m_hash.remove(key);
      m_list.removeAt(pos);
   }

   updateHashPosition(pos, -1, true);
   return true;
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::removeByIndex(long index)
{
   if (index < 0 || index >= size()) { return false; }

   {
      QMutexLocker locker(& m_mutex);
      const Key & key = m_list.at(index).first;
      qAssert(m_hash.value(key, -1) == index);
      m_hash.remove(key);
      m_list.removeAt(index);
   }

   updateHashPosition(index, -1, true);
   return true;
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::removeByIndex(long first, long last)
{
   if (first < 0 || first >= size()) { return false; }
   if (last < 0 || last >= size()) { return false; }
   if (first > last) { return false; }
   { QMutexLocker locker(& m_mutex); m_batch = true; }
   for (long idx = first; idx <= last; idx++) { removeByIndex(idx); }
   { QMutexLocker locker(& m_mutex); m_batch = false; }
   updateHashPosition(first);
   return true;
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::removeFirst()
{
   return this->removeByIndex(0);
}

template <typename Key, typename Value>
bool QxCollection<Key, Value>::removeLast()
{
   return this->removeByIndex(size() - 1);
}

template <typename Key, typename Value>
typename QxCollection<Key, Value>::const_reference_value QxCollection<Key, Value>::getByKey(const Key & key) const
{
   qAssert(exist(key));
   QMutexLocker locker(& m_mutex);
   qAssert(m_list.size() == m_hash.size());
   const type_pair_key_value & pair = m_list.at(m_hash.value(key, -1));
   qAssert(pair.first == key);
   return pair.second;
}

template <typename Key, typename Value>
typename QxCollection<Key, Value>::const_reference_value QxCollection<Key, Value>::getByIndex(long index) const
{
   QMutexLocker locker(& m_mutex);
   qAssert(m_list.size() == m_hash.size());
   qAssert((index >= 0) && (index < static_cast<long>(m_list.size())));
   qAssert(m_hash.value(m_list.at(index).first, -1) == index);
   return m_list.at(index).second;
}

template <typename Key, typename Value>
typename QxCollection<Key, Value>::const_reference_value QxCollection<Key, Value>::getFirst() const
{
   qAssert(! empty());
   QMutexLocker locker(& m_mutex);
   qAssert(m_list.size() == m_hash.size());
   return m_list.at(0).second;
}

template <typename Key, typename Value>
typename QxCollection<Key, Value>::const_reference_value QxCollection<Key, Value>::getLast() const
{
   qAssert(! empty());
   QMutexLocker locker(& m_mutex);
   qAssert(m_list.size() == m_hash.size());
   return m_list.at(m_list.size() - 1).second;
}

template <typename Key, typename Value>
typename QxCollection<Key, Value>::const_reference_key QxCollection<Key, Value>::getKeyByIndex(long index) const
{
   QMutexLocker locker(& m_mutex);
   qAssert(m_list.size() == m_hash.size());
   qAssert((index >= 0) && (index < static_cast<long>(m_list.size())));
   qAssert(m_hash.value(m_list.at(index).first, -1) == index);
   return m_list.at(index).first;
}

template <typename Key, typename Value>
void QxCollection<Key, Value>::sortByKey(bool bAscending /* = true */)
{
   if (bAscending)   { QMutexLocker locker(& m_mutex); std::sort(m_list.begin(), m_list.end(), (& compareKeyValue<std::is_pointer<Key>::value || qx::trait::is_smart_ptr<Key>::value, 0>::compareByKeyAscending)); }
   else              { QMutexLocker locker(& m_mutex); std::sort(m_list.begin(), m_list.end(), (& compareKeyValue<std::is_pointer<Key>::value || qx::trait::is_smart_ptr<Key>::value, 0>::compareByKeyDescending)); }
   updateHashPosition(0, -1, true);
}

template <typename Key, typename Value>
void QxCollection<Key, Value>::sortByValue(bool bAscending /* = true */)
{
   if (bAscending)   { QMutexLocker locker(& m_mutex); std::sort(m_list.begin(), m_list.end(), (& compareKeyValue<std::is_pointer<Value>::value || qx::trait::is_smart_ptr<Value>::value, 0>::compareByValueAscending)); }
   else              { QMutexLocker locker(& m_mutex); std::sort(m_list.begin(), m_list.end(), (& compareKeyValue<std::is_pointer<Value>::value || qx::trait::is_smart_ptr<Value>::value, 0>::compareByValueDescending)); }
   updateHashPosition(0, -1, true);
}

} // namespace qx

template <typename Key, typename Value>
QDataStream & operator<< (QDataStream & stream, const qx::QxCollection<Key, Value> & t)
{
   long lCount = t.count();
   stream << (qint32)(lCount);

   for (long l = 0; l < lCount; l++)
   {
      stream << t.getKeyByIndex(l);
      stream << t.getByIndex(l);
   }

   return stream;
}

template <typename Key, typename Value>
QDataStream & operator>> (QDataStream & stream, qx::QxCollection<Key, Value> & t)
{
   qint32 lCount = 0;
   stream >> lCount;
   t.clear();
   t.reserve(lCount);

   for (qint32 l = 0; l < lCount; l++)
   {
      Key key;       stream >> key;
      Value value;   stream >> value;
      t.insert(key, value);
   }

   return stream;
}
