/// \file
/// \ingroup tutorial_eve_7
///
/// This is an example of visualization of containers
/// with REveDataCollection and REveDataProxyBuilders.
/// \macro_code
///

#include "ROOT/REveDataTable.hxx"
#include "ROOT/REveDataSimpleProxyBuilderTemplate.hxx"
#include "ROOT/REveManager.hxx"
#include "ROOT/REveScalableStraightLineSet.hxx"
#include "ROOT/REveViewContext.hxx"
#include <ROOT/REveGeoShape.hxx>
#include <ROOT/REveJetCone.hxx>
#include <ROOT/REvePointSet.hxx>
#include <ROOT/REveProjectionBases.hxx>
#include <ROOT/REveProjectionManager.hxx>
#include <ROOT/REveScene.hxx>
#include <ROOT/REveTableProxyBuilder.hxx>
#include <ROOT/REveTableInfo.hxx>
#include <ROOT/REveTrack.hxx>
#include <ROOT/REveTrackPropagator.hxx>
#include <ROOT/REveViewer.hxx>
#include <ROOT/REveViewContext.hxx>
#include <ROOT/REveBoxSet.hxx>
#include <ROOT/REveSelection.hxx>
#include <ROOT/REveCalo.hxx>

#include "TGeoTube.h"
#include "TROOT.h"
#include "TList.h"
#include "TParticle.h"
#include "TRandom.h"
#include "TApplication.h"
#include "TFile.h"
#include "TH2F.h"
#include <iostream>

const Double_t kR_min = 299;
const Double_t kR_max = 300;
const Double_t kZ_d = 500;

namespace fw3dlego {
const int xbins_n = 83;
const double xbins[xbins_n] = {
   -5.191, -4.889, -4.716, -4.538, -4.363, -4.191, -4.013, -3.839, -3.664, -3.489, -3.314, -3.139, -2.964, -2.853,
   -2.650, -2.500, -2.322, -2.172, -2.043, -1.930, -1.830, -1.740, -1.653, -1.566, -1.479, -1.392, -1.305, -1.218,
   -1.131, -1.044, -0.957, -0.870, -0.783, -0.696, -0.609, -0.522, -0.435, -0.348, -0.261, -0.174, -0.087, 0.000,
   0.087,  0.174,  0.261,  0.348,  0.435,  0.522,  0.609,  0.696,  0.783,  0.870,  0.957,  1.044,  1.131,  1.218,
   1.305,  1.392,  1.479,  1.566,  1.653,  1.740,  1.830,  1.930,  2.043,  2.172,  2.322,  2.500,  2.650,  2.853,
   2.964,  3.139,  3.314,  3.489,  3.664,  3.839,  4.013,  4.191,  4.363,  4.538,  4.716,  4.889,  5.191};
} // namespace fw3dlego

ROOT::Experimental::REveManager *eveMng = nullptr;
ROOT::Experimental::REveProjectionManager *g_projMng = nullptr;
using namespace ROOT::Experimental;

//==============================================================================
//============== EMULATE FRAMEWORK CLASSES =====================================
//==============================================================================

// a demo class, can be provided from experiment framework
class Jet : public TParticle {
public:
   float fEtaSize{0};
   float fPhiSize{0};

   float GetEtaSize() const { return fEtaSize; }
   float GetPhiSize() const { return fPhiSize; }
   void SetEtaSize(float iEtaSize) { fEtaSize = iEtaSize; }
   void SetPhiSize(float iPhiSize) { fPhiSize = iPhiSize; }

   Jet(Int_t pdg, Int_t status, Int_t mother1, Int_t mother2, Int_t daughter1, Int_t daughter2, Double_t px,
       Double_t py, Double_t pz, Double_t etot)
      : TParticle(pdg, status, mother1, mother2, daughter1, daughter2, px, py, pz, etot, 0, 0, 0, 0)
   {
   }

   ClassDef(Jet, 1);
};

class RecHit : public TObject {
public:
   float fX{0};
   float fY{0};
   float fZ{0};
   float fPt{0};

   RecHit(float pt, float x, float y, float z) : fPt(pt), fX(x), fY(y), fZ(z) {}
   ClassDef(RecHit, 1);
};

class RCaloTower : public TObject {
public:
   float fEta{0};
   float fPhi{0};
   float fEt{0};

   RCaloTower(float eta, float phi, float et) : fEta(eta), fPhi(phi), fEt(et) {}
   ClassDef(RCaloTower, 1);
};

class REveCaloTowerSliceSelector : public REveCaloDataSliceSelector {
private:
   REveDataCollection *fCollection{nullptr};
   REveCaloDataHist *fCaloData{nullptr};

public:
   REveCaloTowerSliceSelector(int s, REveDataCollection *c, REveCaloDataHist *h)
      : REveCaloDataSliceSelector(s), fCollection(c), fCaloData(h)
   {
   }

   using REveCaloDataSliceSelector::ProcessSelection;
   void ProcessSelection(REveCaloData::vCellId_t &sel_cells, UInt_t selectionId, Bool_t multi) override
   {
      std::set<int> item_set;
      REveCaloData::CellData_t cd;
      for (auto &cellId : sel_cells) {
         fCaloData->GetCellData(cellId, cd);

         // loop over enire collection and check its eta/phi range
         for (int t = 0; t < fCollection->GetNItems(); ++t) {
            RCaloTower *tower = (RCaloTower *)fCollection->GetDataPtr(t);
            if (tower->fEta > cd.fEtaMin && tower->fEta < cd.fEtaMax && tower->fPhi > cd.fPhiMin &&
                tower->fPhi < cd.fPhiMax && fCollection->GetDataItem(t)->GetVisible()) {
               item_set.insert(t);
            }
         }
      }
      REveSelection *sel = (REveSelection *)eveMng->FindElementById(selectionId);
      fCollection->GetItemList()->RefSelectedSet() = item_set;
      sel->NewElementPicked(fCollection->GetItemList()->GetElementId(), multi, true, item_set);
   }

   using REveCaloDataSliceSelector::GetCellsFromSecondaryIndices;
   void GetCellsFromSecondaryIndices(const std::set<int> &idcs, REveCaloData::vCellId_t &out) override
   {
      TH2F *hist = fCaloData->GetHist(GetSliceIndex());
      std::set<int> cbins;
      float total = 0;
      for (auto &i : idcs) {
         RCaloTower *tower = (RCaloTower *)fCollection->GetDataPtr(i);
         int bin = hist->FindBin(tower->fEta, tower->fPhi);
         float frac = tower->fEt / hist->GetBinContent(bin);
         bool ex = false;
         for (size_t ci = 0; ci < out.size(); ++ci) {
            if (out[ci].fTower == bin && out[ci].fSlice == GetSliceIndex()) {
               float oldv = out[ci].fFraction;
               out[ci].fFraction = oldv + frac;
               ex = true;
               break;
            }
         }
         if (!ex) {
            out.push_back(REveCaloData::CellId_t(bin, GetSliceIndex(), frac));
         }
      }
   }
};

class Event {
public:
   int eventId{0};
   int N_tracks{0};
   int N_jets{0};
   std::vector<TList *> fListData;

   REveCaloDataHist *fCaloData{nullptr};

   Event()
   {
      auto baseHist = new TH2F("dummy", "dummy", fw3dlego::xbins_n - 1, fw3dlego::xbins, 72, -TMath::Pi(), TMath::Pi());
      fCaloData = new REveCaloDataHist();
      fCaloData->AddHistogram(baseHist);

      auto selector = new REveCaloDataSelector();
      fCaloData->SetSelector(selector);

      eveMng->GetEventScene()->AddElement(fCaloData);
   }

   void MakeJets(int N)
   {
      TRandom &r = *gRandom;
      r.SetSeed(0);
      TList *list = new TList();
      list->SetName("Jets");
      for (int i = 1; i <= N; ++i) {
         double pt = r.Uniform(0.5, 10);
         double eta = r.Uniform(-2.55, 2.55);
         double phi = r.Uniform(-TMath::Pi(), TMath::Pi());

         double px = pt * std::cos(phi);
         double py = pt * std::sin(phi);
         double pz = pt * (1. / (std::tan(2 * std::atan(std::exp(-eta)))));

         auto jet = new Jet(0, 0, 0, 0, 0, 0, px, py, pz, std::sqrt(px * px + py * py + pz * pz + 80 * 80));
         jet->SetEtaSize(r.Uniform(0.02, 0.2));
         jet->SetPhiSize(r.Uniform(0.01, 0.3));
         list->Add(jet);
      }
      fListData.push_back(list);
   }

   void MakeParticles(int N)
   {
      TRandom &r = *gRandom;
      r.SetSeed(0);
      TList *list = new TList();
      list->SetName("Tracks");
      for (int i = 1; i <= N; ++i) {
         double pt = r.Uniform(0.5, 10);
         double eta = r.Uniform(-2.55, 2.55);
         double phi = r.Uniform(0, TMath::TwoPi());

         double px = pt * std::cos(phi);
         double py = pt * std::sin(phi);
         double pz = pt * (1. / (std::tan(2 * std::atan(std::exp(-eta)))));

         // printf("Event::MakeParticles %2d: pt=%.2f, eta=%.2f, phi=%.2f\n", i, pt, eta, phi);
         auto particle =
            new TParticle(0, 0, 0, 0, 0, 0, px, py, pz, std::sqrt(px * px + py * py + pz * pz + 80 * 80), 0, 0, 0, 0);

         int pdg = 11 * (r.Integer(2) > 0 ? 1 : -1);
         particle->SetPdgCode(pdg);

         list->Add(particle);
      }
      fListData.push_back(list);
   }

   void MakeRecHits(int N)
   {
      TRandom &r = *gRandom;
      r.SetSeed(0);
      TList *list = new TList();
      list->SetName("RecHits");

      for (int i = 1; i <= N; ++i) {
         float pt = r.Uniform(0.5, 10);
         float x = r.Uniform(-200, 200);
         float y = r.Uniform(-200, 200);
         float z = r.Uniform(-500, 500);
         auto rechit = new RecHit(pt, x, y, z);
         list->Add(rechit);
      }
      fListData.push_back(list);
   }

   void Clear()
   {
      for (auto &l : fListData)
         delete l;
      fListData.clear();
   }

   void Create()
   {
      Clear();
      MakeJets(4);
      MakeParticles(100);
      MakeRecHits(20);

      // refill calo data from jet list
      TList *jlist = fListData[0];
      TList *elist = new TList();
      elist->SetName("ECAL");
      fListData.push_back(elist);
      TList *hlist = new TList();
      hlist->SetName("HCAL");
      fListData.push_back(hlist);
      for (int i = 0; i <= jlist->GetLast(); ++i) {
         const Jet *j = (Jet *)jlist->At(i);
         float offX = j->Eta();
         float offY = j->Phi() > TMath::Pi() ? j->Phi() - TMath::TwoPi() : j->Phi();
         for (int k = 0; k < 20; ++k) {
            double x, y, v;
            x = gRandom->Uniform(-j->GetEtaSize(), j->GetEtaSize());
            y = gRandom->Uniform(-j->GetPhiSize(), j->GetPhiSize());
            v = j->Pt();
            auto etower = new RCaloTower(offX + x, offY + y, v + gRandom->Uniform(2, 3));
            elist->Add(etower);
            auto htower = new RCaloTower(offX + x, offY + y, v + gRandom->Uniform(1, 2));
            hlist->Add(htower);
         }
      }
      fCaloData->DataChanged();
      eventId++;
   }
};

//==============================================================================
//== PROXY BUILDERS ============================================================
//==============================================================================

class JetProxyBuilder : public REveDataSimpleProxyBuilderTemplate<Jet> {
   bool HaveSingleProduct() const override { return false; }

   using REveDataSimpleProxyBuilderTemplate<Jet>::BuildItemViewType;
   void BuildItemViewType(const Jet &dj, int idx, REveElement *iItemHolder, const std::string &viewType,
                          const REveViewContext *context) override
   {
      auto jet = new REveJetCone();
      jet->SetCylinder(context->GetMaxR(), context->GetMaxZ());
      jet->AddEllipticCone(dj.Eta(), dj.Phi(), dj.GetEtaSize(), dj.GetPhiSize());
      SetupAddElement(jet, iItemHolder, true);
      jet->SetLineColor(jet->GetMainColor());

      float size = 50.f * dj.Pt(); // values are saved in scale
      double theta = dj.Theta();
      // printf("%s jet theta =  %f, phi = %f \n",  iItemHolder->GetCName(), theta, dj.Phi());
      double phi = dj.Phi();

      if (viewType == "Projected") {
         static const float_t offr = 6;
         float r_ecal = context->GetMaxR() + offr;
         float z_ecal = context->GetMaxZ() + offr;

         float transAngle = abs(atan(r_ecal / z_ecal));
         double r = 0;
         bool debug = false;
         if (theta < transAngle || 3.14 - theta < transAngle) {
            z_ecal = context->GetMaxZ() + offr / transAngle;
            r = z_ecal / fabs(cos(theta));
         } else {
            debug = true;
            r = r_ecal / sin(theta);
         }

         REveVector p1(0, (phi < TMath::Pi() ? r * fabs(sin(theta)) : -r * fabs(sin(theta))), r * cos(theta));
         REveVector p2(0, (phi < TMath::Pi() ? (r + size) * fabs(sin(theta)) : -(r + size) * fabs(sin(theta))),
                       (r + size) * cos(theta));

         auto marker = new REveScalableStraightLineSet("jetline");
         marker->SetScaleCenter(p1.fX, p1.fY, p1.fZ);
         marker->AddLine(p1, p2);
         marker->SetLineWidth(4);
         if (debug)
            marker->AddMarker(0, 0.9);

         SetupAddElement(marker, iItemHolder, true);
         marker->SetName(Form("line %s %d", Collection()->GetCName(), idx));
      }
   }

   using REveDataProxyBuilderBase::LocalModelChanges;

   void LocalModelChanges(int idx, REveElement *el, const REveViewContext *ctx) override
   {
      // printf("LocalModelChanges jet %s ( %s )\n", el->GetCName(), el->FirstChild()->GetCName());
      REveJetCone *cone = dynamic_cast<REveJetCone *>(el->FirstChild());
      cone->SetLineColor(cone->GetMainColor());
   }
};

class TParticleProxyBuilder : public REveDataSimpleProxyBuilderTemplate<TParticle> {
   using REveDataSimpleProxyBuilderTemplate<TParticle>::Build;

   void BuildItem(const TParticle &p, int idx, REveElement *iItemHolder, const REveViewContext *context) override
   {
      const TParticle *x = &p;
      auto track = new REveTrack((TParticle *)(x), 1, context->GetPropagator());
      track->MakeTrack();
      SetupAddElement(track, iItemHolder, true);
   }
};

class RecHitProxyBuilder : public REveDataProxyBuilderBase {
private:
   class FWBoxSet : public REveBoxSet {
   public:
      using REveElement::GetSelectionMaster;
      REveElement *GetSelectionMaster() override
      {
         if (fSelectionMaster) {
            REveDataItemList *il = dynamic_cast<REveDataItemList *>(fSelectionMaster);
            il->RefSelectedSet() = RefSelectedSet();
            return il;
         }
         return nullptr;
      }
   };

   REveBoxSet *fBoxSet{nullptr};
   void buildBoxSet(REveBoxSet *boxset)
   {
      auto collection = Collection();
      boxset->SetMainColor(collection->GetMainColor());
      boxset->SetName(collection->GetCName());
      boxset->SetPickable(true);
      boxset->SetAlwaysSecSelect(true);
      boxset->SetDetIdsAsSecondaryIndices(true);
      boxset->SetSelectionMaster(((REveDataCollection *)collection)->GetItemList());
      boxset->Reset(REveBoxSet::kBT_FreeBox, true, collection->GetNItems());
      TRandom r(0);

#define RND_BOX(x) (Float_t) r.Uniform(-(x), (x))
      for (int h = 0; h < collection->GetNItems(); ++h) {
         RecHit *hit = (RecHit *)collection->GetDataPtr(h);
         const REveDataItem *item = Collection()->GetDataItem(h);

         Float_t x = hit->fX;
         Float_t y = hit->fY;
         Float_t z = hit->fZ;
         Float_t a = hit->fPt;
         Float_t d = 0.05;
         Float_t verts[24] = {x - a + RND_BOX(d), y - a + RND_BOX(d), z - a + RND_BOX(d), x - a + RND_BOX(d),
                              y + a + RND_BOX(d), z - a + RND_BOX(d), x + a + RND_BOX(d), y + a + RND_BOX(d),
                              z - a + RND_BOX(d), x + a + RND_BOX(d), y - a + RND_BOX(d), z - a + RND_BOX(d),
                              x - a + RND_BOX(d), y - a + RND_BOX(d), z + a + RND_BOX(d), x - a + RND_BOX(d),
                              y + a + RND_BOX(d), z + a + RND_BOX(d), x + a + RND_BOX(d), y + a + RND_BOX(d),
                              z + a + RND_BOX(d), x + a + RND_BOX(d), y - a + RND_BOX(d), z + a + RND_BOX(d)};
         boxset->AddBox(verts);

         boxset->DigitValue(item->GetVisible() ? 1 : 0);
         if (item->GetVisible())
            boxset->DigitColor(item->GetMainColor());
      }
      boxset->RefitPlex();
      boxset->StampObjProps();
   }

public:
   using REveDataProxyBuilderBase::Build;
   void BuildProduct(const REveDataCollection *collection, REveElement *product, const REveViewContext *) override
   {
      fBoxSet = new FWBoxSet();
      buildBoxSet(fBoxSet);
      product->AddElement(fBoxSet);
   }

   using REveDataProxyBuilderBase::FillImpliedSelected;
   void FillImpliedSelected(REveElement::Set_t &impSet, const std::set<int> &sec_idcs, Product *p) override
   {
      //  printf("RecHit fill implioed ----------------- !!!%zu\n",
      //  Collection()->GetItemList()->RefSelectedSet().size());
      impSet.insert(fBoxSet);
   }

   using REveDataProxyBuilderBase::ModelChanges;
   void ModelChanges(const REveDataCollection::Ids_t &ids, Product *product) override
   {
      for (auto &i : ids) {
         auto digi = fBoxSet->GetDigit(i);
         auto item = Collection()->GetDataItem(i);
         fBoxSet->SetCurrentDigit(i);
         if (item->GetVisible()) {
            fBoxSet->DigitValue(1);
            fBoxSet->DigitColor(item->GetMainColor());
         } else {
            fBoxSet->DigitValue(0);
         }
      }
      fBoxSet->StampObjProps();
   }
}; // RecHitProxyBuilder

class CaloTowerProxyBuilder : public REveDataProxyBuilderBase {
private:
   REveCaloDataHist *fCaloData{nullptr};
   TH2F *fHist{nullptr};
   int fSliceIndex{-1};

   void assertSlice()
   {
      if (!fHist) {
         Bool_t status = TH1::AddDirectoryStatus();

         TDirectory::TContext ctx{nullptr}; // Don't register histograms to the current directory
         fHist = new TH2F("caloHist", "caloHist", fw3dlego::xbins_n - 1, fw3dlego::xbins, 72, -M_PI, M_PI);
         fSliceIndex = fCaloData->AddHistogram(fHist);

         fCaloData->RefSliceInfo(fSliceIndex)
            .Setup(Collection()->GetCName(), 0., Collection()->GetMainColor(), Collection()->GetMainTransparency());

         fCaloData->GetSelector()->AddSliceSelector(std::unique_ptr<REveCaloDataSliceSelector>(
            new REveCaloTowerSliceSelector(fSliceIndex, Collection(), fCaloData)));
      }
   }

public:
   CaloTowerProxyBuilder(REveCaloDataHist *cd) : fCaloData(cd) {}

   using REveDataProxyBuilderBase::Build;
   void BuildProduct(const REveDataCollection *collection, REveElement *product, const REveViewContext *) override
   {
      assertSlice();
      fHist->Reset();
      if (collection->GetRnrSelf()) {
         fCaloData->RefSliceInfo(fSliceIndex)
            .Setup(Collection()->GetCName(), 0., Collection()->GetMainColor(), Collection()->GetMainTransparency());

         for (int h = 0; h < collection->GetNItems(); ++h) {
            RCaloTower *tower = (RCaloTower *)collection->GetDataPtr(h);
            const REveDataItem *item = Collection()->GetDataItem(h);

            if (!item->GetVisible())
               continue;
            fHist->Fill(tower->fEta, tower->fPhi, tower->fEt);
         }
      }
      fCaloData->DataChanged();
   }

   using REveDataProxyBuilderBase::FillImpliedSelected;
   void FillImpliedSelected(REveElement::Set_t &impSet, const std::set<int> &sec_idcs, Product *) override
   {
      fCaloData->GetSelector()->SetActiveSlice(fSliceIndex);
      impSet.insert(fCaloData);
      fCaloData->FillImpliedSelectedSet(impSet, sec_idcs);
   }

   using REveDataProxyBuilderBase::ModelChanges;
   void ModelChanges(const REveDataCollection::Ids_t &ids, Product *product) override
   {
      BuildProduct(Collection(), nullptr, nullptr);
   }

}; // CaloTowerProxyBuilder

//==============================================================================
//== COLLECTION MANGER  ================================================================
//==============================================================================

class CollectionManager {
private:
   Event *fEvent{nullptr};

   std::vector<REveScene *> m_scenes;
   REveViewContext *m_viewContext{nullptr};

   std::vector<REveDataProxyBuilderBase *> m_builders;

   REveScene *m_collections{nullptr};
   bool m_inEventLoading{false};

public:
   CollectionManager(Event *event) : fEvent(event)
   {
      // view context
      float r = 300;
      float z = 300;
      auto prop = new REveTrackPropagator();
      prop->SetMagFieldObj(new REveMagFieldDuo(350, 3.5, -2.0));
      prop->SetMaxR(r);
      prop->SetMaxZ(z);
      prop->SetMaxOrbs(6);
      prop->IncRefCount();

      m_viewContext = new REveViewContext();
      m_viewContext->SetBarrel(r, z);
      m_viewContext->SetTrackPropagator(prop);

      // table specs
      auto tableInfo = new REveTableViewInfo();

      tableInfo->table("TParticle").column("pt", 1, "i.Pt()").column("eta", 3, "i.Eta()").column("phi", 3, "i.Phi()");

      tableInfo->table("Jet")
         .column("eta", 1, "i.Eta()")
         .column("phi", 1, "i.Phi()")
         .column("etasize", 2, "i.GetEtaSize()")
         .column("phisize", 2, "i.GetPhiSize()");

      tableInfo->table("RecHit").column("pt", 1, "i.fPt");

      tableInfo->table("RCaloTower").column("eta", 3, "i.fEta").column("phi", 3, "i.fPhi").column("Et", 3, "i.fEt");

      m_viewContext->SetTableViewInfo(tableInfo);

      for (auto &c : eveMng->GetScenes()->RefChildren()) {
         if (c != eveMng->GetGlobalScene() && strncmp(c->GetCName(), "Geometry", 8)) {
            m_scenes.push_back((REveScene *)c);
         }
         if (!strncmp(c->GetCName(), "Table", 5))
            c->AddElement(m_viewContext->GetTableViewInfo());
      }

      m_collections = eveMng->SpawnNewScene("Collections", "Collections");
   }

   void SetDataItemsFromEvent(REveDataCollection *collection)
   {
      for (auto &l : fEvent->fListData) {
         TIter next(l);
         if (collection->GetName() == std::string(l->GetName())) {
            collection->ClearItems();

            for (int i = 0; i <= l->GetLast(); ++i) {
               std::string cname = collection->GetName();
               auto len = cname.size();
               char end = cname[len - 1];
               if (end == 's') {
                  cname = cname.substr(0, len - 1);
               }
               TString pname(Form("%s %2d", cname.c_str(), i));
               collection->AddItem(l->At(i), pname.Data(), "");
            }
         }
         collection->ApplyFilter();
      }
   }

   void LoadEvent()
   {
      m_inEventLoading = true;

      for (auto &el : m_collections->RefChildren()) {
         auto c = dynamic_cast<REveDataCollection *>(el);
         SetDataItemsFromEvent(c);
      }

      for (auto proxy : m_builders) {
         proxy->Build();
      }

      fEvent->fCaloData->DataChanged();
      m_inEventLoading = false;
   }

   void addCollection(REveDataCollection *collection, REveDataProxyBuilderBase *glBuilder, bool showInTable = false)
   {
      m_collections->AddElement(collection);

      // load data
      SetDataItemsFromEvent(collection);
      glBuilder->SetCollection(collection);
      glBuilder->SetHaveAWindow(true);
      for (auto scene : m_scenes) {
         if (strncmp(scene->GetCName(), "Tables", 5) == 0)
            continue;

         REveElement *product = glBuilder->CreateProduct(scene->GetTitle(), m_viewContext);

         if (!strncmp(scene->GetCTitle(), "Projected", 8)) {
            g_projMng->ImportElements(product, scene);
         } else {
            scene->AddElement(product);
         }
      }
      m_builders.push_back(glBuilder);
      glBuilder->Build();

      // Tables
      auto tableBuilder = new REveTableProxyBuilder();
      tableBuilder->SetHaveAWindow(true);
      tableBuilder->SetCollection(collection);
      REveElement *tablep = tableBuilder->CreateProduct("table-type", m_viewContext);
      auto tableMng = m_viewContext->GetTableViewInfo();
      if (showInTable) {
         tableMng->SetDisplayedCollection(collection->GetElementId());
      }

      for (auto s : m_scenes) {
         if (strncmp(s->GetCTitle(), "Table", 5) == 0) {
            s->AddElement(tablep);
            tableBuilder->Build();
         }
      }
      tableMng->AddDelegate([=]() { tableBuilder->ConfigChanged(); });
      m_builders.push_back(tableBuilder);

      // set tooltip expression for items
      auto tableEntries = tableMng->RefTableEntries(collection->GetItemClass()->GetName());
      int N = TMath::Min(int(tableEntries.size()), 3);
      for (int t = 0; t < N; t++) {
         auto te = tableEntries[t];
         collection->GetItemList()->AddTooltipExpression(te.fName, te.fExpression);
      }

      collection->GetItemList()->SetItemsChangeDelegate(
         [&](REveDataItemList *collection, const REveDataCollection::Ids_t &ids) {
            this->ModelChanged(collection, ids);
         });
      collection->GetItemList()->SetFillImpliedSelectedDelegate(
         [&](REveDataItemList *collection, REveElement::Set_t &impSelSet, const std::set<int> &sec_idcs) {
            this->FillImpliedSelected(collection, impSelSet, sec_idcs);
         });
   }

   void finishViewCreate()
   {
      auto mngTable = m_viewContext->GetTableViewInfo();
      if (mngTable) {
         for (auto &el : m_collections->RefChildren()) {
            if (el->GetName() == "Tracks")
               mngTable->SetDisplayedCollection(el->GetElementId());
         }
      }
   }

   void ModelChanged(REveDataItemList *itemList, const REveDataCollection::Ids_t &ids)
   {
      if (m_inEventLoading)
         return;

      for (auto proxy : m_builders) {
         if (proxy->Collection()->GetItemList() == itemList) {
            // printf("Model changes check proxy %s: \n", proxy->Type().c_str());
            proxy->ModelChanges(ids);
         }
      }
   }

   void FillImpliedSelected(REveDataItemList *itemList, REveElement::Set_t &impSelSet, const std::set<int> &sec_idcs)
   {
      if (m_inEventLoading)
         return;

      for (auto proxy : m_builders) {
         if (proxy->Collection()->GetItemList() == itemList) {
            proxy->FillImpliedSelected(impSelSet, sec_idcs);
         }
      }
   }
};

//==============================================================================
//== Event Manager =============================================================
//==============================================================================

class EventManager : public REveElement {
private:
   Event *fEvent;
   CollectionManager *fCMng;

public:
   EventManager(Event *e, CollectionManager *m) : fEvent(e), fCMng(m) {}

   ~EventManager() override {}

   virtual void NextEvent()
   {
      eveMng->GetSelection()->ClearSelection();
      eveMng->GetHighlight()->ClearSelection();
      fEvent->Create();
      fCMng->LoadEvent();
   }
};

class FWSelectionDeviator : public REveSelection::Deviator {
public:
   FWSelectionDeviator() {}

   using REveSelection::Deviator::DeviateSelection;
   bool DeviateSelection(REveSelection *selection, REveElement *el, bool multi, bool secondary,
                         const std::set<int> &secondary_idcs) override
   {
      if (el) {
         auto *colItems = dynamic_cast<REveDataItemList *>(el);
         if (colItems) {
            // std::cout << "Deviate RefSelected=" << colItems->RefSelectedSet().size() << " passed set " <<
            // secondary_idcs.size() << "\n";
            ExecuteNewElementPicked(selection, colItems, multi, true, colItems->RefSelectedSet());
            return true;
         }
      }
      return false;
   }
};
//==============================================================================
//== main() ====================================================================
//==============================================================================

void collection_proxies(bool proj = true)
{
   eveMng = REveManager::Create();
   auto event = new Event();
   event->Create();

   // divert selection to map proxy builder products with collection
   auto deviator = std::make_shared<FWSelectionDeviator>();
   eveMng->GetSelection()->SetDeviator(deviator);
   eveMng->GetHighlight()->SetDeviator(deviator);

   // create scenes and views
   REveScene *rhoZEventScene = nullptr;

   auto b1 = new REveGeoShape("Barrel 1");
   b1->SetShape(new TGeoTube(kR_min, kR_max, kZ_d));
   b1->SetMainColor(kCyan);
   b1->SetMainTransparency(90);
   eveMng->GetGlobalScene()->AddElement(b1);

   rhoZEventScene = eveMng->SpawnNewScene("RhoZ Scene", "Projected");
   g_projMng = new REveProjectionManager(REveProjection::kPT_RhoZ);
   g_projMng->SetImportEmpty(true);

   auto rhoZView = eveMng->SpawnNewViewer("RhoZ View");
   rhoZView->SetCameraType(REveViewer::kCameraOrthoXOY);
   rhoZView->AddScene(rhoZEventScene);
   auto pgeoScene = eveMng->SpawnNewScene("Geometry projected");
   rhoZView->AddScene(pgeoScene);
   g_projMng->ImportElements(b1, pgeoScene);

   auto tableScene = eveMng->SpawnNewScene("Tables", "Tables");
   auto tableView = eveMng->SpawnNewViewer("Table", "Table View");
   tableView->AddScene(tableScene);

   // create event data from list
   auto collectionMng = new CollectionManager(event);

   REveDataCollection *trackCollection = new REveDataCollection("Tracks");
   trackCollection->SetItemClass(TParticle::Class());
   trackCollection->SetMainColor(kGreen);
   trackCollection->SetFilterExpr("i.Pt() > 4.1 && std::abs(i.Eta()) < 1");
   collectionMng->addCollection(trackCollection, new TParticleProxyBuilder(), true);

   REveDataCollection *jetCollection = new REveDataCollection("Jets");
   jetCollection->SetItemClass(Jet::Class());
   jetCollection->SetMainColor(kYellow);
   jetCollection->SetFilterExpr("i.Pt() > 1");
   collectionMng->addCollection(jetCollection, new JetProxyBuilder());

   REveDataCollection *hitCollection = new REveDataCollection("RecHits");
   hitCollection->SetItemClass(RecHit::Class());
   hitCollection->SetMainColor(kOrange + 7);
   hitCollection->SetFilterExpr("i.fPt > 5");
   collectionMng->addCollection(hitCollection, new RecHitProxyBuilder(), true);

   // add calorimeters
   auto calo3d = new REveCalo3D(event->fCaloData);
   calo3d->SetBarrelRadius(kR_max);
   calo3d->SetEndCapPos(kZ_d);
   calo3d->SetMaxTowerH(300);
   eveMng->GetEventScene()->AddElement(calo3d);
   REveCalo2D *calo2d = (REveCalo2D *)g_projMng->ImportElements(calo3d, rhoZEventScene);

   REveDataCollection *ecalCollection = new REveDataCollection("ECAL");
   ecalCollection->SetItemClass(RCaloTower::Class());
   ecalCollection->SetMainColor(kRed);
   collectionMng->addCollection(ecalCollection, new CaloTowerProxyBuilder(event->fCaloData));

   REveDataCollection *hcalCollection = new REveDataCollection("HCAL");
   hcalCollection->SetItemClass(RCaloTower::Class());
   hcalCollection->SetMainColor(kBlue);
   collectionMng->addCollection(hcalCollection, new CaloTowerProxyBuilder(event->fCaloData));

   // event navigation
   auto eventMng = new EventManager(event, collectionMng);
   eventMng->SetName("EventManager");
   eveMng->GetWorld()->AddElement(eventMng);

   eveMng->GetWorld()->AddCommand("NextEvent", "sap-icon://step", eventMng, "NextEvent()");

   eveMng->Show();
}
