// @(#)root/gl:$Id$
// Author:  Matevz Tadel, Feb 2007

/*************************************************************************
 * Copyright (C) 1995-2004, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

#include "TGLViewerBase.h"

#include "TGLSceneBase.h"
#include "TGLSceneInfo.h"

#include "TGLRnrCtx.h"
#include "TGLCamera.h"
#include "TGLClip.h"
#include "TGLOverlay.h"
#include "TGLSelectBuffer.h"
#include "TGLSelectRecord.h"
#include "TGLAnnotation.h"
#include "TGLUtil.h"

#include "TGLContext.h"
#include "TGLIncludes.h"

#include "TEnv.h"

#include <algorithm>
#include <stdexcept>

//______________________________________________________________________
//
// Base class for GL viewers. Provides a basic scene management and a
// small set of control variables (camera, LOD, style, clip) that are
// used by the scene classes. Renering wrappers are available but
// minimal.
//
// There is no concept of GL-context here ... we just draw
// into whatever is set from outside.
//
// Development notes:
//
// Each viewer automatically creates a TGLRnrCtx and passes it down
// all render functions.

ClassImp(TGLViewerBase);

//______________________________________________________________________
TGLViewerBase::TGLViewerBase() :
   fRnrCtx    (0),
   fCamera    (0),
   fClip      (0),
   fLOD       (TGLRnrCtx::kLODHigh),
   fStyle     (TGLRnrCtx::kFill),
   fWFLineW   (1),
   fOLLineW   (1),

   fResetSceneInfosOnRender (kFALSE),
   fChanged                 (kFALSE)
{
   // Constructor.

   fRnrCtx = new TGLRnrCtx(this);

   fWFLineW = gEnv->GetValue("OpenGL.WireframeLineScalingFactor", 1.0);
   fOLLineW = gEnv->GetValue("OpenGL.OutlineLineScalingFactor", 1.0);
}

//______________________________________________________________________
TGLViewerBase::~TGLViewerBase()
{
   // Destructor.

   for (SceneInfoList_i i=fScenes.begin(); i!=fScenes.end(); ++i)
   {
      (*i)->GetScene()->RemoveViewer(this);
      delete *i;
   }

   DeleteOverlayElements(TGLOverlayElement::kAll);

   delete fRnrCtx;
}

//______________________________________________________________________________
const char* TGLViewerBase::LockIdStr() const
{
   // Name to print in locking output.

   return "TGLViewerBase";
}

/**************************************************************************/
// Scene & scene-info management
/**************************************************************************/

//______________________________________________________________________
TGLViewerBase::SceneInfoList_i
TGLViewerBase::FindScene(TGLSceneBase* scene)
{
   // Find scene-info corresponding to scene.

   SceneInfoList_i i = fScenes.begin();
   while (i != fScenes.end() && (*i)->GetScene() != scene) ++i;
   return i;
}

//______________________________________________________________________
TGLSceneInfo* TGLViewerBase::AddScene(TGLSceneBase* scene)
{
   // Add new scene, appropriate scene-info is created.

   SceneInfoList_i i = FindScene(scene);
   if (i == fScenes.end()) {
      TGLSceneInfo* sinfo = scene->CreateSceneInfo(this);
      fScenes.push_back(sinfo);
      scene->AddViewer(this);
      Changed();
      return sinfo;
   } else {
      Warning("TGLViewerBase::AddScene", "scene '%s' already in the list.",
              scene->GetName());
      return 0;
   }
}

//______________________________________________________________________
void TGLViewerBase::RemoveScene(TGLSceneBase* scene)
{
   // Remove scene from the viewer, its scene-info is deleted.

   SceneInfoList_i i = FindScene(scene);
   if (i != fScenes.end()) {
      delete *i;
      fScenes.erase(i);
      scene->RemoveViewer(this);
      Changed();
   } else {
      Warning("TGLViewerBase::RemoveScene", "scene '%s' not found.",
              scene->GetName());
   }
}

//______________________________________________________________________
void TGLViewerBase::RemoveAllScenes()
{
   // Remove all scenes from the viewer, their scene-infos are deleted.

   for (SceneInfoList_i i=fScenes.begin(); i!=fScenes.end(); ++i)
   {
      TGLSceneInfo * sinfo = *i;
      sinfo->GetScene()->RemoveViewer(this);
      delete sinfo;
   }
   fScenes.clear();
   Changed();
}

//______________________________________________________________________
void TGLViewerBase::SceneDestructing(TGLSceneBase* scene)
{
   // Remove scene, its scene-info is deleted.
   // Called from scene that is being destroyed while still holding
   // viewer references.

   SceneInfoList_i i = FindScene(scene);
   if (i != fScenes.end()) {
      delete *i;
      fScenes.erase(i);
      Changed();
   } else {
      Warning("TGLViewerBase::SceneDestructing", "scene not found.");
   }
}

//______________________________________________________________________
TGLSceneInfo* TGLViewerBase::GetSceneInfo(TGLSceneBase* scene)
{
   // Find scene-info corresponding to scene.

   SceneInfoList_i i = FindScene(scene);
   if (i != fScenes.end())
      return *i;
   else
      return 0;
}

//______________________________________________________________________________
TGLLogicalShape* TGLViewerBase::FindLogicalInScenes(TObject* id)
{
   // Find logical-shape representing object id in the list of scenes.
   // Return 0 if not found.

   for (SceneInfoList_i i=fScenes.begin(); i!=fScenes.end(); ++i)
   {
      TGLLogicalShape *lshp = (*i)->GetScene()->FindLogical(id);
      if (lshp)
         return lshp;
   }
   return 0;
}

//______________________________________________________________________
void TGLViewerBase::AddOverlayElement(TGLOverlayElement* el)
{
   // Add overlay element.

   fOverlay.push_back(el);
   Changed();
}

//______________________________________________________________________
void TGLViewerBase::RemoveOverlayElement(TGLOverlayElement* el)
{
   // Remove overlay element.

   OverlayElmVec_i it = std::find(fOverlay.begin(), fOverlay.end(), el);
   if (it != fOverlay.end())
      fOverlay.erase(it);
   Changed();
}

//______________________________________________________________________
void TGLViewerBase::DeleteOverlayAnnotations()
{
   // Delete overlay elements that are annotations.

   DeleteOverlayElements(TGLOverlayElement::kAnnotation);
}

//______________________________________________________________________
void TGLViewerBase::DeleteOverlayElements(TGLOverlayElement::ERole role)
{
   // Delete overlay elements.

   OverlayElmVec_t ovl;
   fOverlay.swap(ovl);

   for (OverlayElmVec_i i = ovl.begin(); i != ovl.end(); ++i)
   {
      if (role == TGLOverlayElement::kAll || (*i)->GetRole() == role)
         delete *i;
      else
         fOverlay.push_back(*i);
   }

   Changed();
}

/**************************************************************************/
// SceneInfo update / check
/**************************************************************************/

//______________________________________________________________________________
void TGLViewerBase::ResetSceneInfos()
{
   // Force rebuild of view-dependent scene-info structures.
   //
   // This should be called before calling render (draw/select) if
   // something that affects camera interest has been changed.

   SceneInfoList_i i = fScenes.begin();
   while (i != fScenes.end())
   {
      (*i)->ResetSceneStamp();
      ++i;
   }
}

//______________________________________________________________________________
void TGLViewerBase::MergeSceneBBoxes(TGLBoundingBox& bbox)
{
   // Merge bounding-boxes of all active registered scenes.

   bbox.SetEmpty();
   for (SceneInfoList_i i=fScenes.begin(); i!=fScenes.end(); ++i)
   {
      TGLSceneInfo * sinfo = *i;
      if (sinfo->GetActive())
      {
         sinfo->SetupTransformsAndBBox(); // !!! transform not done yet, no camera
         bbox.MergeAligned(sinfo->GetTransformedBBox());
      }
   }
}

/**************************************************************************/
// Rendering / selection virtuals
/**************************************************************************/

//______________________________________________________________________________
void TGLViewerBase::SetupClipObject()
{
   // Setup clip-object. Protected virtual method.

   if (fClip)
   {
      fClip->Setup(fOverallBoundingBox);
   }
}

//______________________________________________________________________
void TGLViewerBase::PreRender()
{
   // Initialize render-context, setup camera, GL, render-area.
   // Check and lock scenes, determine their visibility.

   TGLContextIdentity* cid = TGLContextIdentity::GetCurrent();
   if (cid == 0)
   {
      // Assume derived class set it up for us.
      // This happens due to complex implementation
      // of gl-in-pad using gGLManager.
      // In principle we should throw an exception:
      // throw std::runtime_error("Can not resolve GL context.");
   }
   else
   {
      if (cid != fRnrCtx->GetGLCtxIdentity())
      {
         if (fRnrCtx->GetGLCtxIdentity() != 0)
            Warning("TGLViewerBase::PreRender", "Switching to another GL context; maybe you should use context-sharing.");
         fRnrCtx->SetGLCtxIdentity(cid);
      }
   }

   fRnrCtx->SetCamera        (fCamera);
   fRnrCtx->SetViewerLOD     (fLOD);
   fRnrCtx->SetViewerStyle   (fStyle);
   fRnrCtx->SetViewerWFLineW (fWFLineW);
   fRnrCtx->SetViewerOLLineW (fOLLineW);
   fRnrCtx->SetViewerClip    (fClip);

   if (fResetSceneInfosOnRender)
   {
      ResetSceneInfos();
      fResetSceneInfosOnRender = kFALSE;
   }

   fOverallBoundingBox.SetEmpty();
   SceneInfoList_t locked_scenes;
   for (SceneInfoList_i i=fScenes.begin(); i!=fScenes.end(); ++i)
   {
      TGLSceneInfo *sinfo = *i;
      TGLSceneBase *scene = sinfo->GetScene();
      if (sinfo->GetActive())
      {
         if ( ! fRnrCtx->Selection() || scene->GetSelectable())
         {
            if ( ! sinfo->GetScene()->TakeLock(kDrawLock))
            {
               Warning("TGLViewerBase::PreRender", "locking of scene '%s' failed, skipping.",
                       sinfo->GetScene()->GetName());
               continue;
            }
            locked_scenes.push_back(sinfo);
         }
         sinfo->SetupTransformsAndBBox(); // !!! transform not done yet
         fOverallBoundingBox.MergeAligned(sinfo->GetTransformedBBox());
      }
   }

   fCamera->Apply(fOverallBoundingBox, fRnrCtx->GetPickRectangle());
   SetupClipObject();

   // Make precursory selection of visible scenes.
   // Only scene bounding-box .vs. camera frustum check performed.
   fVisScenes.clear();
   for (SceneInfoList_i i=locked_scenes.begin(); i!=locked_scenes.end(); ++i)
   {
      TGLSceneInfo         * sinfo = *i;
      const TGLBoundingBox & bbox  = sinfo->GetTransformedBBox();
      Bool_t visp = (!bbox.IsEmpty() && fCamera->FrustumOverlap(bbox) != Rgl::kOutside);
      sinfo->ViewCheck(visp);
      if (visp) {
         fRnrCtx->SetSceneInfo(sinfo);
         sinfo->GetScene()->PreDraw(*fRnrCtx);
         if (sinfo->IsVisible()) {
            fVisScenes.push_back(sinfo);
         } else {
            sinfo->GetScene()->PostDraw(*fRnrCtx);
            sinfo->GetScene()->ReleaseLock(kDrawLock);
         }
         fRnrCtx->SetSceneInfo(0);
      } else {
         sinfo->GetScene()->ReleaseLock(kDrawLock);
      }
   }
}

//______________________________________________________________________________
void TGLViewerBase::SubRenderScenes(SubRender_foo render_foo)
{
   // Call sub-rendering function render_foo on all currently visible
   // scenes.

   Int_t nScenes = fVisScenes.size();

   for (Int_t i = 0; i < nScenes; ++i)
   {
      TGLSceneInfo* sinfo = fVisScenes[i];
      TGLSceneBase* scene = sinfo->GetScene();
      fRnrCtx->SetSceneInfo(sinfo);
      glPushName(i);
      scene->PreRender(*fRnrCtx);
      (scene->*render_foo)(*fRnrCtx);
      scene->PostRender(*fRnrCtx);
      glPopName();
      fRnrCtx->SetSceneInfo(0);
   }
}

//______________________________________________________________________
void TGLViewerBase::Render()
{
   // Render all scenes. This is done in two main passes:
   // - render opaque objects from all scenes
   // - render transparent objects from all scenes

   RenderOpaque();
   RenderTransparent();
}

//______________________________________________________________________
void TGLViewerBase::RenderNonSelected()
{
   // Render non-selected objects from all scenes.

   SubRenderScenes(&TGLSceneBase::RenderOpaque);

   TGLCapabilityEnabler blend(GL_BLEND, kTRUE);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   glDepthMask(GL_FALSE);

   SubRenderScenes(&TGLSceneBase::RenderTransp);

   glDepthMask(GL_TRUE);

   TGLUtil::CheckError("TGLViewerBase::RenderNonSelected - pre exit check");
}

//______________________________________________________________________
void TGLViewerBase::RenderSelected()
{
   // Render selected objects from all scenes.

   SubRenderScenes(&TGLSceneBase::RenderSelOpaque);

   TGLCapabilityEnabler blend(GL_BLEND, kTRUE);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   glDepthMask(GL_FALSE);

   SubRenderScenes(&TGLSceneBase::RenderSelTransp);

   glDepthMask(GL_TRUE);

   TGLUtil::CheckError("TGLViewerBase::RenderSelected - pre exit check");
}

//______________________________________________________________________________
void TGLViewerBase::RenderSelectedForHighlight()
{
   // Render selected objects from all scenes for highlight.

   fRnrCtx->SetHighlight(kTRUE);

   SubRenderScenes(&TGLSceneBase::RenderSelOpaqueForHighlight);

   TGLCapabilityEnabler blend(GL_BLEND, kTRUE);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   glDepthMask(GL_FALSE);

   SubRenderScenes(&TGLSceneBase::RenderSelTranspForHighlight);

   glDepthMask(GL_TRUE);

   fRnrCtx->SetHighlight(kFALSE);
}

//______________________________________________________________________
void TGLViewerBase::RenderOpaque(Bool_t rnr_non_selected, Bool_t rnr_selected)
{
   // Render opaque objects from all scenes.

   if (rnr_non_selected)
   {
      SubRenderScenes(&TGLSceneBase::RenderOpaque);
   }
   if (rnr_selected)
   {
      SubRenderScenes(&TGLSceneBase::RenderSelOpaque);
   }

   TGLUtil::CheckError("TGLViewerBase::RenderOpaque - pre exit check");
}

//______________________________________________________________________
void TGLViewerBase::RenderTransparent(Bool_t rnr_non_selected, Bool_t rnr_selected)
{
   // Render transparent objects from all scenes.

   TGLCapabilityEnabler blend(GL_BLEND, kTRUE);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   glDepthMask(GL_FALSE);

   if (rnr_non_selected)
   {
      SubRenderScenes(&TGLSceneBase::RenderTransp);
   }
   if (rnr_selected)
   {
      SubRenderScenes(&TGLSceneBase::RenderSelTransp);
   }

   glDepthMask(GL_TRUE);

   TGLUtil::CheckError("TGLViewerBase::RenderTransparent - pre exit check");
}

//______________________________________________________________________
void TGLViewerBase::RenderOverlay(Int_t state, Bool_t selection)
{
   // Render overlay objects.

   Int_t nOvl = fOverlay.size();
   for (Int_t i = 0; i < nOvl; ++i)
   {
      TGLOverlayElement* el = fOverlay[i];
      if (el->GetState() & state)
      {
         if (selection) glPushName(i);
         el->Render(*fRnrCtx);
         if (selection) glPopName();
      }
   }
}

//______________________________________________________________________
void TGLViewerBase::PostRender()
{
   // Function called after rendering is finished.
   // Here we just unlock the scenes.

   for (SceneInfoVec_i i = fVisScenes.begin(); i != fVisScenes.end(); ++i)
   {
      TGLSceneInfo* sinfo = *i;
      fRnrCtx->SetSceneInfo(sinfo);
      sinfo->GetScene()->PostDraw(*fRnrCtx);
      fRnrCtx->SetSceneInfo(0);
      sinfo->GetScene()->ReleaseLock(kDrawLock);
   }
   fChanged = kFALSE;
}

//______________________________________________________________________
void TGLViewerBase::PreRenderOverlaySelection()
{
   // Perform minimal initialization for overlay selection.
   // Here we assume that scene has already been drawn and that
   // camera and overall bounding box are ok.
   // Scenes are not locked.

   fCamera->Apply(fOverallBoundingBox, fRnrCtx->GetPickRectangle());
}

//______________________________________________________________________
void TGLViewerBase::PostRenderOverlaySelection()
{
   // Perform cleanup after overlay selection.

}

/**************************************************************************/
// High-level functions: drawing and picking.
/**************************************************************************/


//______________________________________________________________________
//void TGLViewerBase::Select(Int_t selX, Int_t selY, Int_t selRadius)
//{
   // Perform render-pass in selection mode.
   // Process the selection results.
   // For now only in derived classes.
//}

//______________________________________________________________________
Bool_t TGLViewerBase::ResolveSelectRecord(TGLSelectRecord& rec, Int_t recIdx)
{
   // Process selection record on buffer-position 'recIdx' and
   // fill the data into 'rec'.
   //
   // Returns TRUE if scene was demangled and an object identified.
   // When FALSE is returned it is still possible that scene has been
   // identified. Check for this if interested in scene-selection.
   //
   // The select-buffer is taken form fRnrCtx.

   TGLSelectBuffer* sb = fRnrCtx->GetSelectBuffer();
   if (recIdx >= sb->GetNRecords())
       return kFALSE;

   if (sb->SelectRecord(rec, recIdx) < 1)
      return kFALSE;

   UInt_t sceneIdx = rec.GetItem(0);
   if (sceneIdx >= fVisScenes.size())
       return kFALSE;

   TGLSceneInfo* sinfo = fVisScenes[sceneIdx];
   rec.SetSceneInfo(sinfo);
   return sinfo->GetScene()->ResolveSelectRecord(rec, 1);
}

//______________________________________________________________________
Bool_t TGLViewerBase::FindClosestRecord(TGLSelectRecord& rec, Int_t& recIdx)
{
   // Find next select record that can be resolved, starting from
   // position 'recIdx'.
   // 'recIdx' is passed as reference and points to found record in the buffer.

   TGLSelectBuffer* sb = fRnrCtx->GetSelectBuffer();

   while (recIdx < sb->GetNRecords())
   {
      if (ResolveSelectRecord(rec, recIdx))
         return kTRUE;
      ++recIdx;
   }
   return kFALSE;
}

//______________________________________________________________________
Bool_t TGLViewerBase::FindClosestOpaqueRecord(TGLSelectRecord& rec, Int_t& recIdx)
{
   // Find next select record that can be resolved and whose result is
   // not transparent, starting from position 'recIdx'.
   // 'recIdx' is passed as reference and points to found record in the buffer.

   TGLSelectBuffer* sb = fRnrCtx->GetSelectBuffer();

   while (recIdx < sb->GetNRecords())
   {
      if (ResolveSelectRecord(rec, recIdx) && ! rec.GetTransparent())
         return kTRUE;
      ++recIdx;
   }
   return kFALSE;
}

//______________________________________________________________________
Bool_t TGLViewerBase::FindClosestOverlayRecord(TGLOvlSelectRecord& rec,
                                               Int_t             & recIdx)
{
   // Find next overlay-select record that can be resolved, starting from
   // position 'recIdx'.
   // 'recIdx' is passed as reference and points to found record in the buffer.

   TGLSelectBuffer* sb = fRnrCtx->GetSelectBuffer();

   while (recIdx < sb->GetNRecords())
   {
      sb->SelectRecord(rec, recIdx);
      if (rec.GetItem(0) < fOverlay.size())
      {
         rec.SetOvlElement(fOverlay[rec.GetItem(0)]);
         rec.NextPos();
         return kTRUE;
      }
      ++recIdx;
   }
   return kFALSE;
}