//
// ********************************************************************
// * License and Disclaimer                                           *
// *                                                                  *
// * The  Geant4 software  is  copyright of the Copyright Holders  of *
// * the Geant4 Collaboration.  It is provided  under  the terms  and *
// * conditions of the Geant4 Software License,  included in the file *
// * LICENSE and available at  http://cern.ch/geant4/license .  These *
// * include a list of copyright holders.                             *
// *                                                                  *
// * Neither the authors of this software system, nor their employing *
// * institutes,nor the agencies providing financial support for this *
// * work  make  any representation or  warranty, express or implied, *
// * regarding  this  software system or assume any liability for its *
// * use.  Please see the license in the file  LICENSE  and URL above *
// * for the full disclaimer and the limitation of liability.         *
// *                                                                  *
// * This  code  implementation is the result of  the  scientific and *
// * technical work of the GEANT4 collaboration.                      *
// * By using,  copying,  modifying or  distributing the software (or *
// * any work based  on the software)  you  agree  to acknowledge its *
// * use  in  resulting  scientific  publications,  and indicate your *
// * acceptance of all terms of the Geant4 Software license.          *
// ********************************************************************
//
/*
 * G4ManyFastLists.hh
 *
 *  Created on: 17 nov. 2014
 *      Author: kara
 */

#pragma once

#include "G4FastList.hh"
#include <set>

template<class OBJECT>
  struct G4ManyFastLists_iterator;

/*
 * Roll over many list as if it was one
 */
template<class OBJECT>
  class G4ManyFastLists : public G4FastList<OBJECT>::Watcher
  {
  protected:
    using ManyLists = G4FastList<G4FastList<OBJECT>>;
    ManyLists fAssociatedLists;
    // TODO use "marked list" insted of vector

    using WatcherSet = std::set<typename G4FastList<OBJECT>::Watcher*,
        sortWatcher<OBJECT>>;
    WatcherSet* fMainListWatchers;

  public:
    using iterator = G4ManyFastLists_iterator<OBJECT>;

    G4ManyFastLists() : G4FastList<OBJECT>::Watcher(),
        fAssociatedLists(), fMainListWatchers(nullptr)
    {
    }

    ~G4ManyFastLists() override = default;

    virtual void NotifyDeletingList(G4FastList<OBJECT>* __list)
    {
      fAssociatedLists.pop(__list);
    }

    void AddGlobalWatcher(typename G4FastList<OBJECT>::Watcher* watcher)
    {
      if(fMainListWatchers == nullptr)
      {
        fMainListWatchers = new WatcherSet();
      }

      fMainListWatchers->insert(watcher);

      typename ManyLists::iterator it = fAssociatedLists.begin();
      typename ManyLists::iterator _end = fAssociatedLists.end();

      for(;it != _end ;++it)
      {
        watcher->Watch(*it);
//        (*it)->AddWatcher(watcher);
//        (*it)->AddWatcher(watcher);
      }
    }

    inline void Add(G4FastList<OBJECT>* __list)
    {
      if (__list == nullptr) return;
      fAssociatedLists.push_back(__list); // TODO use the table doubling tech
      //__list->AddWatcher(this);
      this->Watch(__list);

      if(fMainListWatchers == nullptr) return;

      auto it_watcher = fMainListWatchers->begin();
      auto end_watcher = fMainListWatchers->end();

//      G4cout << "G4ManyFastLists::Add -- N watchers ="
//             << fMainListWatchers->size()
//             << G4endl;

      for(;it_watcher != end_watcher ;++it_watcher)
      {
//        G4cout << " *** *** *** WATCH --- "
//                           << (*it_watcher)->GetWatcherName()
//                           << G4endl;
        (*it_watcher)->Watch(__list);
      }

      if(__list->empty() == false)
      {
        it_watcher = fMainListWatchers->begin();

        for(;it_watcher != end_watcher ;++it_watcher)
        {
          typename G4FastList<OBJECT>::iterator it_obj = __list->begin();
          for(;it_obj != __list->end() ;++it_obj)
          {
//            G4cout << " *** *** *** NOTIFY ADD OBJ --- "
//                   << (*it_watcher)->GetWatcherName()
//                   << G4endl;

            (*it_watcher)->NotifyAddObject(*it_obj,__list);
          }
        }
      }
//      else
//      {
//        G4cout << "__list->empty() == true" << G4endl;
//      }

      /*
      typename ManyLists::const_iterator __it = fAssociatedLists
          .begin();
      typename ManyLists::const_iterator __end = fAssociatedLists
          .end();
      for (; __it != __end; __it++)
      {
        assert(*__it);
      }
      */
    }

    inline void Remove(G4FastList<OBJECT>* __list)
    {
      if (__list == nullptr) return;
      fAssociatedLists.pop(__list); // TODO use the table doubling tech
      __list->RemoveWatcher(this);
      this->StopWatching(__list);

      auto it = fMainListWatchers->begin();
      auto _end = fMainListWatchers->end();

      for(;it != _end ;++it)
      {
        (*it)->StopWatching(__list);
      }

//      typename ManyLists::node* __node = __list->GetListNode();
//      if(__node)
//      {
//        __list->SetListNode(0);
//        delete __node;
//      }
    }

    inline bool Holds(OBJECT* __track) const
    {
      typename ManyLists::const_iterator __it = fAssociatedLists.begin();
      typename ManyLists::const_iterator __end = fAssociatedLists.end();
      for (; __it != __end; __it++)
        if ((*__it)->Holds(__track)) return true;
      return false;
    }

    inline size_t size() const
    {
      size_t __size(0);
      for (auto __it : fAssociatedLists)
      {
        __size += __it->size();
      }
      return __size;
    }

    inline void RemoveLists()
    {
      typename ManyLists::iterator __it = fAssociatedLists.begin();
      typename ManyLists::iterator __end = fAssociatedLists.end();
      for (; __it != __end; __it++)
      {
        if (*__it)
        {
          (*__it)->clear();
          typename ManyLists::iterator next = __it;
          next++;
          Remove(*__it);
          typename ManyLists::node* __node = __it.GetNode();
          if(__node)
          {
            __node->GetObject()->SetListNode(nullptr);
            delete __node;
          }
//          delete (*__it);

          __it = next;
        }
      }
      fAssociatedLists.clear();
    }

    inline void ClearLists()
    {
      typename ManyLists::iterator __it = fAssociatedLists.begin();
      typename ManyLists::iterator __end = fAssociatedLists.end();
      for (; __it != __end; __it++)
        if (*__it) (*__it)->clear();
    }

    inline iterator begin();
    inline iterator end();

    void pop(OBJECT*);
  };

template<class OBJECT>
  struct G4ManyFastLists_iterator
  {
    using ManyLists = G4FastList<G4FastList<OBJECT>>;

    using _Self = G4ManyFastLists_iterator;
    using _Node = G4FastListNode<OBJECT>;

    G4FastList_iterator<OBJECT> fIterator;
    typename ManyLists::iterator fCurrentListIt;
    ManyLists* fLists;

  private:
    G4ManyFastLists_iterator() = default;

  public:
    explicit G4ManyFastLists_iterator(G4FastList_iterator<OBJECT> __x,
                                      typename ManyLists::iterator __it,
                                      ManyLists* __lists) :
        fIterator(__x), fCurrentListIt(__it), fLists(__lists)
    {
    }

    G4ManyFastLists_iterator(const G4ManyFastLists_iterator& __x) = default;
    _Self& operator=(const G4ManyFastLists_iterator& __x) = default;

    _Node* GetNode()
    {
      return fIterator.GetNode();
    }

    G4FastList<OBJECT>* GetTrackList()
    {
      return *fCurrentListIt;
    }

    OBJECT* operator*()
    {
      return *fIterator;
    }
    const OBJECT* operator*() const
    {
      return *fIterator;
    }
    OBJECT* operator->()
    {
      return *fIterator;
    }
    const OBJECT* operator->() const
    {
      return *fIterator;
    }

    _Self UpdateToNextValidList();
    _Self& operator++();

    _Self operator++(int)
    {
      return operator++();
    }

    _Self&
    operator--()
    {
      if (fLists->empty())
      {
        fIterator = G4FastList_iterator<OBJECT>();
        return *this;
      }
      if (fCurrentListIt == fLists->begin())
      {
        if (fIterator == (*fCurrentListIt)->begin())
        {
          fIterator = G4FastList_iterator<OBJECT>();
          return *this;
        }
      }

      if (fCurrentListIt == fLists->end())
      {
        fCurrentListIt--;
        fIterator = (*fCurrentListIt)->end();
      }
      else if (fIterator == (*fCurrentListIt)->begin())
      {
        fCurrentListIt--;
        fIterator = (*fCurrentListIt)->end();
      }

      fIterator--;

      while (((*fCurrentListIt)->empty() || fIterator.GetNode() == nullptr
              || fIterator.GetNode()->GetObject() == nullptr)
             && fCurrentListIt != fLists->begin())
      {
        fIterator = (*fCurrentListIt)->begin();
        fCurrentListIt--;
        fIterator = (*fCurrentListIt)->end();
        fIterator--;
      }

      if (fIterator.GetNode() == nullptr && fCurrentListIt == fLists->begin())
      {
        fIterator = G4FastList_iterator<OBJECT>();
        return *this;
      }

      return *this;
    }

    _Self operator--(int)
    {
      return operator--();
    }

    G4bool operator==(const _Self& __x) const
    {
      return (fIterator == __x.fIterator && fCurrentListIt == __x.fCurrentListIt);
    } // Fast check

    G4bool operator!=(const _Self& __x) const
    {
      return !(this->operator ==(__x));
    }

  protected:
    void HasReachedEnd()
    {
      if (fLists->empty() == false)
      {
        fIterator = (*(fLists->end()--))->end();
      }
      else
      {
        fIterator = G4FastList_iterator<OBJECT>();
      }
    }
  };

template<class OBJECT>
  typename G4ManyFastLists<OBJECT>::iterator G4ManyFastLists<OBJECT>::begin()
  {
    if (fAssociatedLists.empty())
    {
      return G4ManyFastLists_iterator<OBJECT>(G4FastList_iterator<OBJECT>(),
                                             fAssociatedLists.end(),
                                             &fAssociatedLists);
    }

    typename G4FastList<OBJECT>::iterator trackList_it;
    int i = 0;

    typename ManyLists::iterator it = fAssociatedLists.begin();
    typename ManyLists::iterator _end = fAssociatedLists.end();

    while (it != _end)
    {
      if (*it && (*it)->empty() == false)
      {
        trackList_it = (*it)->begin();
        break;
      }
      i++;
      it++;
    };

    if (i == fAssociatedLists.size() || it == _end)
    {
      return end();
    }

    return G4ManyFastLists_iterator<OBJECT>(trackList_it,
//                                           fAssociatedLists.begin(),
                                            it,
                                           &fAssociatedLists);
  }

template<class OBJECT>
typename G4ManyFastLists<OBJECT>::iterator G4ManyFastLists<OBJECT>::end()
  {
    if (fAssociatedLists.empty())
    {
      return G4ManyFastLists_iterator<OBJECT>(G4FastList_iterator<OBJECT>(),
                                             fAssociatedLists.end(),
                                             &fAssociatedLists);
    }

    return G4ManyFastLists_iterator<OBJECT>((fAssociatedLists.end()--)->end(),
                                           fAssociatedLists.end(),
                                           &fAssociatedLists);
  }

#include "G4ManyFastLists.icc"
