// -*- C++ -*-
//
// Package:     Core
// Class  :     FWProxyBuilderBase
//
// Implementation:
//     [Notes on implementation]
//
// Original Author:  Chris Jones, Matevz Tadel, Alja Mrak-Tadel
//         Created:  Thu Mar 18 14:12:00 CET 2010
//

// system include files
#include <iostream>
#include <functional>

// user include files
#include "TEveElement.h"
#include "TEveCompound.h"
#include "TEveManager.h"
#include "TEveProjectionManager.h"
#include "TEveSelection.h"

#include "Fireworks/Core/interface/FWProxyBuilderBase.h"
#include "Fireworks/Core/interface/FWEventItem.h"
#include "Fireworks/Core/interface/FWModelId.h"
#include "Fireworks/Core/interface/FWInteractionList.h"
#include "Fireworks/Core/interface/FWViewContext.h"
#include "Fireworks/Core/interface/fwLog.h"

//
// constants, enums and typedefs
//

//
// static data member definitions
//

//
// constructors and destructor
//

FWProxyBuilderBase::Product::Product(FWViewType::EType t, const FWViewContext* c)
    : m_viewType(t), m_viewContext(c), m_elements(nullptr) {
  m_elements = new TEveElementList("ProxyProduct");
  m_elements->IncDenyDestroy();
}

FWProxyBuilderBase::Product::~Product() {
  // remove product from projected scene (RhoPhi or RhoZ)
  TEveProjectable* pable = dynamic_cast<TEveProjectable*>(m_elements);
  // don't have to check cast, because TEveElementList is TEveProjectable
  for (TEveProjectable::ProjList_i i = pable->BeginProjecteds(); i != pable->EndProjecteds(); ++i) {
    TEveElement* projected = (*i)->GetProjectedAsElement();
    (*projected->BeginParents())->RemoveElement(projected);
  }

  // remove from 3D scenes
  while (m_elements->HasParents()) {
    TEveElement* parent = *m_elements->BeginParents();
    parent->RemoveElement(m_elements);
  }

  m_elements->Annihilate();
}

//______________________________________________________________________________

FWProxyBuilderBase::FWProxyBuilderBase()
    : m_interactionList(nullptr),
      m_item(nullptr),
      m_modelsChanged(false),
      m_haveWindow(false),
      m_mustBuild(true),
      m_layer(0) {}

FWProxyBuilderBase::~FWProxyBuilderBase() { m_products.clear(); }

//
// member functions
//

void FWProxyBuilderBase::setItem(const FWEventItem* iItem) { m_item = iItem; }

void FWProxyBuilderBase::setHaveWindow(bool iFlag) {
  bool oldValue = m_haveWindow;
  m_haveWindow = iFlag;

  if (iFlag && !oldValue) {
    //this is our first view so may need to rerun our building
    if (m_mustBuild) {
      build();
    }
  }
}

void FWProxyBuilderBase::itemBeingDestroyed(const FWEventItem* iItem) {
  m_item = nullptr;

  cleanLocal();

  for (Product_it i = m_products.begin(); i != m_products.end(); i++) {
    (*i)->m_scaleConnection.disconnect();
    delete (*i);
  }

  m_products.clear();
}

void FWProxyBuilderBase::build() {
  if (m_item) {
    try {
      size_t itemSize = m_item->size();  //cashed

      clean();
      for (Product_it i = m_products.begin(); i != m_products.end(); ++i) {
        //printf("build() %s \n", m_item->name().c_str());
        TEveElementList* elms = (*i)->m_elements;
        size_t oldSize = elms->NumChildren();

        if (haveSingleProduct()) {
          build(m_item, elms, (*i)->m_viewContext);
        } else {
          buildViewType(m_item, elms, (*i)->m_viewType, (*i)->m_viewContext);
        }

        // Project all children of current product.
        // If product is not registered into any projection-manager,
        // this does nothing.
        TEveProjectable* pable = dynamic_cast<TEveProjectable*>(elms);
        if (pable->HasProjecteds()) {
          // loop projected holders
          for (TEveProjectable::ProjList_i i = pable->BeginProjecteds(); i != pable->EndProjecteds(); ++i) {
            TEveProjectionManager* pmgr = (*i)->GetManager();
            Float_t oldDepth = pmgr->GetCurrentDepth();
            pmgr->SetCurrentDepth(item()->layer());
            size_t cnt = 0;

            TEveElement* projectedAsElement = (*i)->GetProjectedAsElement();
            TEveElement::List_i parentIt = projectedAsElement->BeginChildren();
            for (TEveElement::List_i prodIt = elms->BeginChildren(); prodIt != elms->EndChildren(); ++prodIt, ++cnt) {
              // reused projected holder
              if (cnt < oldSize) {
                if ((*parentIt)->NumChildren()) {
                  // update projected (mislleading name)
                  for (TEveElement::List_i pci = (*parentIt)->BeginChildren(); pci != (*parentIt)->EndChildren(); pci++)
                    pmgr->ProjectChildrenRecurse(*parentIt);
                } else {
                  // import projectable
                  pmgr->SubImportChildren(*prodIt, *parentIt);
                }

                ++parentIt;
              } else if (cnt < itemSize) {
                // new product holder
                pmgr->SubImportElements(*prodIt, projectedAsElement);
              } else {
                break;
              }
            }
            pmgr->SetCurrentDepth(oldDepth);
          }
        }

        if (m_interactionList && itemSize > oldSize) {
          TEveElement::List_i elIt = elms->BeginChildren();
          for (size_t cnt = 0; cnt < itemSize; ++cnt, ++elIt) {
            if (cnt >= oldSize)
              m_interactionList->added(*elIt, cnt);
          }
        }
      }
    } catch (const std::runtime_error& iException) {
      fwLog(fwlog::kError) << "Caught exception in build function for item " << m_item->name() << ":\n"
                           << iException.what() << std::endl;
      exit(1);
    }
  }
  m_mustBuild = false;
}

//______________________________________________________________________________
void FWProxyBuilderBase::modelChanges(const FWModelIds& iIds, Product* p) {
  TEveElementList* elms = p->m_elements;
  assert(m_item && static_cast<int>(m_item->size()) <= elms->NumChildren() &&
         "can not use default modelChanges implementation");

  TEveElement::List_i itElement = elms->BeginChildren();
  int index = 0;
  for (FWModelIds::const_iterator it = iIds.begin(), itEnd = iIds.end(); it != itEnd; ++it, ++itElement, ++index) {
    assert(itElement != elms->EndChildren());
    while (index < it->index()) {
      ++itElement;
      ++index;
      assert(itElement != elms->EndChildren());
    }
    if (visibilityModelChanges(*it, *itElement, p->m_viewType, p->m_viewContext)) {
      elms->ProjectChild(*itElement);
    } else {
      localModelChanges(*it, *itElement, p->m_viewType, p->m_viewContext);
    }
  }
}

void FWProxyBuilderBase::modelChanges(const FWModelIds& iIds) {
  if (m_haveWindow) {
    for (Product_it i = m_products.begin(); i != m_products.end(); ++i) {
      modelChanges(iIds, *i);
    }
    m_modelsChanged = false;
  } else {
    m_modelsChanged = true;
  }
}

//______________________________________________________________________________
void FWProxyBuilderBase::itemChanged(const FWEventItem* iItem) {
  if (iItem->layer() != m_layer)
    setProjectionLayer(iItem->layer());

  if (m_haveWindow) {
    build();
  } else {
    m_mustBuild = true;
  }
  m_modelsChanged = false;
}

//______________________________________________________________________________
bool FWProxyBuilderBase::canHandle(const FWEventItem& item) {
  if (m_item)
    return (item.purpose() == m_item->purpose());

  return false;
}

//______________________________________________________________________________

TEveElementList* FWProxyBuilderBase::createProduct(const FWViewType::EType viewType, const FWViewContext* viewContext) {
  if (havePerViewProduct(viewType) == false && m_products.empty() == false) {
    if (haveSingleProduct()) {
      return m_products.back()->m_elements;
    } else {
      for (Product_it i = m_products.begin(); i != m_products.end(); ++i) {
        if (viewType == (*i)->m_viewType)
          return (*i)->m_elements;
      }
    }
  }

  // printf("new product %s for item %s \n", FWViewType::idToName(viewType).c_str(), item()->name().c_str()); fflush(stdout);

  Product* product = new Product(viewType, viewContext);
  m_products.push_back(product);
  if (viewContext) {
    product->m_scaleConnection =
        viewContext->scaleChanged_.connect(std::bind(&FWProxyBuilderBase::scaleChanged, this, std::placeholders::_1));
  }

  if (item()) {
    // debug info in eve browser
    product->m_elements->SetElementName(item()->name().c_str());
  }
  return product->m_elements;
}

//______________________________________________________________________________

void FWProxyBuilderBase::removePerViewProduct(FWViewType::EType type, const FWViewContext* vc) {
  for (Product_it i = m_products.begin(); i != m_products.end(); ++i) {
    if (havePerViewProduct(type) && (*i)->m_viewContext == vc) {
      if ((*i)->m_elements)
        (*i)->m_elements->DestroyElements();

      if ((*i)->m_viewContext)
        (*i)->m_scaleConnection.disconnect();

      delete (*i);
      m_products.erase(i);
      break;
    }
  }
}

//------------------------------------------------------------------------------

void FWProxyBuilderBase::setInteractionList(FWInteractionList* l, const std::string& /*purpose*/) {
  // Called if willHandleInteraction() returns false. Purpose ignored by default.

  m_interactionList = l;
}

bool FWProxyBuilderBase::visibilityModelChanges(const FWModelId&,
                                                TEveElement*,
                                                FWViewType::EType,
                                                const FWViewContext*) {
  return false;
}

void FWProxyBuilderBase::localModelChanges(const FWModelId&, TEveElement*, FWViewType::EType, const FWViewContext*) {
  // Nothing to be done in base class.
  // Visibility, main color and main transparency are handled through FWInteractionList.
}

void FWProxyBuilderBase::scaleChanged(const FWViewContext* vc) {
  for (Product_it i = m_products.begin(); i != m_products.end(); ++i) {
    if (havePerViewProduct((*i)->m_viewType) && (*i)->m_viewContext == vc) {
      scaleProduct((*i)->m_elements, (*i)->m_viewType, (*i)->m_viewContext);
    }
  }
  gEve->Redraw3D();
}

void FWProxyBuilderBase::clean() {
  // Cleans local common element list.
  for (Product_it i = m_products.begin(); i != m_products.end(); ++i) {
    if ((*i)->m_elements)
      (*i)->m_elements->DestroyElements();
  }

  cleanLocal();
}

void FWProxyBuilderBase::cleanLocal() {
  // Cleans local common element list.
}

void FWProxyBuilderBase::build(const FWEventItem*, TEveElementList*, const FWViewContext*) {
  assert(
      "virtual build(const FWEventItem*, TEveElementList*, const FWViewContext*) not implemented by inherited class");
}

void FWProxyBuilderBase::buildViewType(const FWEventItem*, TEveElementList*, FWViewType::EType, const FWViewContext*) {
  assert(
      "virtual buildViewType(const FWEventItem*, TEveElementList*, FWViewType::EType, const FWViewContext*) not "
      "implemented by inherited class");
}

void FWProxyBuilderBase::setProjectionLayer(float layer) {
  m_layer = layer;
  for (Product_it pIt = m_products.begin(); pIt != m_products.end(); ++pIt) {
    TEveProjectable* pable = static_cast<TEveProjectable*>((*pIt)->m_elements);
    for (TEveProjectable::ProjList_i i = pable->BeginProjecteds(); i != pable->EndProjecteds(); ++i)
      (*i)->SetDepth(m_layer);
  }
}

//------------------------------------------------------------------------------

void FWProxyBuilderBase::setupAddElement(TEveElement* el, TEveElement* parent, bool color) const {
  setupElement(el, color);
  parent->AddElement(el);
}

/** This method is invoked to setup the per element properties of the various
    objects being drawn.
  */
void FWProxyBuilderBase::setupElement(TEveElement* el, bool color) const {
  el->CSCTakeAnyParentAsMaster();
  el->SetPickable(true);

  if (color) {
    el->CSCApplyMainColorToMatchingChildren();
    el->CSCApplyMainTransparencyToMatchingChildren();
    el->SetMainColor(m_item->defaultDisplayProperties().color());
    assert((m_item->defaultDisplayProperties().transparency() >= 0) &&
           (m_item->defaultDisplayProperties().transparency() <= 100));
    el->SetMainTransparency(m_item->defaultDisplayProperties().transparency());
  }
}

//------------------------------------------------------------------------------

TEveCompound* FWProxyBuilderBase::createCompound(bool set_color, bool propagate_color_to_all_children) const {
  TEveCompound* c = new TEveCompound();
  c->CSCTakeAnyParentAsMaster();
  c->CSCImplySelectAllChildren();
  c->SetPickable(true);
  if (set_color) {
    c->SetMainColor(m_item->defaultDisplayProperties().color());
    c->SetMainTransparency(m_item->defaultDisplayProperties().transparency());
  }
  if (propagate_color_to_all_children) {
    c->CSCApplyMainColorToAllChildren();
    c->CSCApplyMainTransparencyToAllChildren();
  } else {
    c->CSCApplyMainColorToMatchingChildren();
    c->CSCApplyMainTransparencyToMatchingChildren();
  }
  return c;
}

void FWProxyBuilderBase::increaseComponentTransparency(unsigned int index,
                                                       TEveElement* holder,
                                                       const std::string& name,
                                                       Char_t transpOffset) {
  // Helper function to increse transparency of certain components.

  Char_t transparency = item()->modelInfo(index).displayProperties().transparency();
  Char_t transp = TMath::Min(100, transpOffset + (100 - transpOffset) * transparency / 100);
  TEveElement::List_t matches;
  holder->FindChildren(matches, name.c_str());
  for (TEveElement::List_i m = matches.begin(); m != matches.end(); ++m) {
    (*m)->SetMainTransparency(transp);
  }
}

//
// const member functions
//

const fireworks::Context& FWProxyBuilderBase::context() const { return m_item->context(); }

int FWProxyBuilderBase::layer() const { return m_item->layer(); }

//
// static member functions
//

std::string FWProxyBuilderBase::typeOfBuilder() { return std::string(); }

bool FWProxyBuilderBase::representsSubPart() { return false; }
