/* ======================================================================== *\
!
! *
! * This file is part of MARS, the MAGIC Analysis and Reconstruction
! * Software. It is distributed to you in the hope that it can be a useful
! * and timesaving tool in analysing Data of imaging Cerenkov telescopes.
! * It is distributed WITHOUT ANY WARRANTY.
! *
! * Permission to use, copy, modify and distribute this software and its
! * documentation for any purpose is hereby granted without fee,
! * provided that the above copyright notice appear in all copies and
! * that both that copyright notice and this permission notice appear
! * in supporting documentation. It is provided "as is" without express
! * or implied warranty.
! *
!
!
!   Author(s): Thomas Bretz  05/2002 <mailto:tbretz@astro.uni-wuerzburg.de>
!   Author(s): Harald Kornmayer 1/2001
!
!   Copyright: MAGIC Software Development, 2000-2002
!
!
\* ======================================================================== */

/////////////////////////////////////////////////////////////////////////////
//
// MCamDisplay
//
// Camera Display. The Pixels are displayed in
// contents/area [somthing/mm^2]
//
////////////////////////////////////////////////////////////////////////////
#include "MCamDisplay.h"

#include <fstream.h>
#include <iostream.h>

#include <TBox.h>
#include <TArrow.h>
#include <TLatex.h>
#include <TStyle.h>
#include <TMarker.h>
#include <TCanvas.h>
#include <TArrayF.h>
#include <TClonesArray.h>

#include "MH.h"
#include "MHexagon.h"

#include "MGeomCam.h"

#include "MRflEvtData.h"

#include "MCerPhotPix.h"
#include "MCerPhotEvt.h"

#include "MPedestalPix.h"
#include "MPedestalCam.h"

#include "MImgCleanStd.h"

#define kItemsLegend 50 // see SetPalette(1,0)

ClassImp(MCamDisplay);

// ------------------------------------------------------------------------
//
//  Default Constructor. To be used by the root system ONLY.
//
MCamDisplay::MCamDisplay()
    : fGeomCam(NULL), fAutoScale(kTRUE), fW(0), fH(0)
{
    fNumPixels = 0;
    fRange     = 0;

    fPixels    = NULL;
    fLegend    = NULL;
    fLegText   = NULL;
    fArrowX    = NULL;
    fArrowY    = NULL;
    fLegRadius = NULL;
    fLegDegree = NULL;
}

// ------------------------------------------------------------------------
//
//  Constructor. Makes a clone of MGeomCam.
//
MCamDisplay::MCamDisplay(MGeomCam *geom)
    : fGeomCam(NULL), fAutoScale(kTRUE), fW(0), fH(0)
{
    fGeomCam = (MGeomCam*)geom->Clone(); 

    //
    //  create the hexagons of the display
    //
    fNumPixels = fGeomCam->GetNumPixels();
    fRange     = fGeomCam->GetMaxRadius();

    // root 3.02
    //  * base/inc/TObject.h:
    //    register BIT(8) as kNoContextMenu. If an object has this bit set it will
    //    not get an automatic context menu when clicked with the right mouse button.

    fPhotons = new TClonesArray("TMarker", 0);

    //
    // Construct all hexagons. Use new-operator with placement
    //
    fPixels = new TClonesArray("MHexagon", fNumPixels);
    for (UInt_t i=0; i<fNumPixels; i++)
    {
        MHexagon &pix = *new ((*fPixels)[i]) MHexagon((*fGeomCam)[i]);
#if ROOT_VERSION_CODE > ROOT_VERSION(3,01,06)
        pix.SetBit(/*kNoContextMenu|*/kCannotPick);
#endif
        pix.SetFillColor(16);
    }

    //
    // set up the Legend
    //
    const Float_t H = 0.9*fRange;
    const Float_t h = 2./kItemsLegend;

    const Float_t w = fRange/sqrt(fNumPixels);

    fLegend  = new TClonesArray("TBox",  kItemsLegend);
    fLegText = new TClonesArray("TText", kItemsLegend);

    for (Int_t i = 0; i<kItemsLegend; i++)
    {
        TBox  &newbox = *new ((*fLegend)[i])  TBox;
        TText &newtxt = *new ((*fLegText)[i]) TText;

        newbox.SetX1(fRange);
        newbox.SetX2(fRange+w);
        newbox.SetY1(H*( i   *h - 1.));
        newbox.SetY2(H*((i+1)*h - 1.));
        newbox.SetFillColor(16);
#if ROOT_VERSION_CODE > ROOT_VERSION(3,01,06)
        newbox.SetBit(/*kNoContextMenu|*/kCannotPick);
#endif

        newtxt.SetTextSize(0.025);
        newtxt.SetTextAlign(12);
        newtxt.SetX(fRange+1.5*w);
        newtxt.SetY(H*((i+0.5)*h - 1.));
#if ROOT_VERSION_CODE > ROOT_VERSION(3,01,06)
        newtxt.SetBit(/*kNoContextMenu|*/kCannotPick);
#endif
    }

    fArrowX = new TArrow(-fRange*.9, -fRange*.9, -fRange*.6, -fRange*.9, 0.025);
    fArrowY = new TArrow(-fRange*.9, -fRange*.9, -fRange*.9, -fRange*.6, 0.025);

    TString text;
    text += (int)(fRange*.3);
    text += "mm";

    fLegRadius = new TText(-fRange*.85, -fRange*.85, text);
    text = "";
    text += (float)((int)(fRange*.3*fGeomCam->GetConvMm2Deg()*10))/10;
    text += "\\circ";
    text = text.Strip(TString::kLeading);
    fLegDegree = new TLatex(-fRange*.85, -fRange*.75, text);
    fLegRadius->SetTextSize(0.04);
    fLegDegree->SetTextSize(0.04);

#if ROOT_VERSION_CODE < ROOT_VERSION(3,01,06)
    SetPalette(1, 0);
#else
    SetPalette(51, 0);
#endif

}

// ------------------------------------------------------------------------
//
// Destructor. Deletes TClonesArrays for hexagons and legend elements.
//
MCamDisplay::~MCamDisplay()
{
    fPixels->Delete();
    fLegend->Delete();
    fLegText->Delete();
    fPhotons->Delete();

    delete fPixels;
    delete fLegend;
    delete fLegText;
    delete fPhotons;

    delete fArrowX;
    delete fArrowY;

    delete fLegRadius;
    delete fLegDegree;

    delete fGeomCam;
}

inline void MCamDisplay::SetPixColor(const MCerPhotPix &pix, const UInt_t i, Float_t min, Float_t max)
{
    if (i>=fNumPixels)
        return;

    //
    // Fixme: Use absolute value per mm^2. Needs another scaling algorithm.
    //
    const Float_t ratio = fGeomCam->GetPixRatio(i);
    const Float_t pnum  = ratio*pix.GetNumPhotons();

    (*this)[pix.GetPixId()].SetFillColor(GetColor(pnum, min, max));
}

inline void MCamDisplay::SetPixColorPedestal(const MPedestalPix &pix, const UInt_t i, Float_t min, Float_t max)
{
    if (i>=fNumPixels)
        return;

    //
    // Fixme: Use absolute value per mm^2. Needs another scaling algorithm.
    //
    const Float_t ratio = fGeomCam->GetPixRatio(i);
    const Float_t pnum  = ratio*pix.GetMean();

    (*this)[i].SetFillColor(GetColor(pnum, min, max));
}

inline void MCamDisplay::SetPixColorError(const MCerPhotPix &pix, const UInt_t i, Float_t min, Float_t max)
{
    if (i>=fNumPixels)
        return;

    //
    // Fixme: Use absolute value per mm^2. Needs another scaling algorithm.
    //
    const Float_t ratio = fGeomCam->GetPixRatio(i);
    const Float_t pnum  = sqrt(ratio)*pix.GetErrorPhot();

    (*this)[pix.GetPixId()].SetFillColor(GetColor(pnum, min, max));
}

inline void MCamDisplay::SetPixColorRatio(const MCerPhotPix &pix, Float_t min, Float_t max)
{
    //
    // Fixme: Use absolute value per mm^2. Needs another scaling algorithm.
    //
    const Float_t pnum  = pix.GetNumPhotons()/pix.GetErrorPhot();
    (*this)[pix.GetPixId()].SetFillColor(GetColor(pnum, min, max));
}

inline void MCamDisplay::SetPixColorLevel(const MCerPhotPix &pix, Float_t lvl1, Float_t lvl2)
{
    const Int_t maxcolidx = kItemsLegend-1;

    MHexagon &hex = (*this)[pix.GetPixId()];

    const Float_t r = pix.GetNumPhotons()/pix.GetErrorPhot();

    if (r>lvl1)
        hex.SetFillColor(fColors[4*maxcolidx/5]);
    else
        if (r>lvl2)
            hex.SetFillColor(fColors[maxcolidx/2]);
        else
            hex.SetFillColor(fColors[maxcolidx/5]);
}

// ------------------------------------------------------------------------
//
// Call this function to draw the camera layout into your canvas.
// Setup a drawing canvas. Add this object and all child objects
// (hexagons, etc) to the current pad. If no pad exists a new one is
// created.
//
void MCamDisplay::Draw(Option_t *option)
{
    // root 3.02:
    // gPad->SetFixedAspectRatio()

    TVirtualPad *pad = gPad ? gPad : MH::MakeDefCanvas("CamDisplay", "Mars Camera Display", 750, 600);
    pad->SetBorderMode(0);
    pad->SetFillColor(16);

    AppendPad("");
}


void MCamDisplay::SetRange()
{
    //
    // Maintain aspect ratio
    //
    const float ratio = 1.15;

    const float w = gPad->GetWw();
    const float h = gPad->GetWh()*ratio;

    gPad->Range(-fRange, -fRange, (2*ratio-1)*fRange, fRange);

    if (h<w)
        gPad->SetPad((1.-h/w)/2, 0, (h/w+1)/2, 0.9999999);
    else
        gPad->SetPad(0, (1.-w/h)/2, 1, (w/h+1)/2);
}

// ------------------------------------------------------------------------
//
// This is called at any time the canvas should get repainted.
// Here we maintain an aspect ratio of 1.15. This makes sure,
// that the camera image doesn't get distorted by resizing the canvas.
//
void MCamDisplay::Paint(Option_t *opt)
{
    if (!fPixels)
        return;

    //
    // Maintain aspect ratio
    //
    SetRange();

    //
    // Maintain colors
    //
    SetPalette();

    //
    // Paint Legend
    //
    fArrowX->Paint(">");
    fArrowY->Paint(">");

    fLegRadius->Paint();
    fLegDegree->Paint();

    //
    // Paint primitives (pixels, color legend, photons, ...)
    //
    { fPixels->ForEach(TObject, Paint)(); }
    { fLegend->ForEach(TObject, Paint)(); }
    { fLegText->ForEach(TObject, Paint)(); }
    { fPhotons->ForEach(TObject, Paint)(); }
}

// ------------------------------------------------------------------------
//
//  With this function you can change the color palette. For more
// information see TStyle::SetPalette. Only palettes with 50 colors
// are allowed.
// In addition you can use SetPalette(52, 0) to create an inverse
// deep blue sea palette
//
void MCamDisplay::SetPalette(Int_t ncolors, Int_t *colors)
{
    //
    // If not enough colors are specified skip this.
    //
    if (ncolors>1 && ncolors<50)
    {
        cout << "MCamDisplay::SetPalette: Only default palettes with 50 colors are allowed... ignored." << endl;
        return;
    }

    //
    // If ncolors==52 create a reversed deep blue sea palette
    //
    if (ncolors==52)
    {
        gStyle->SetPalette(51, NULL);
        Int_t c[50];
        for (int i=0; i<50; i++)
            c[49-i] = gStyle->GetColorPalette(i);
        gStyle->SetPalette(50, c);
    }
    else
        gStyle->SetPalette(ncolors, colors);

    //
    // Change the colors of the pixels
    //
    for (unsigned int i=0; i<fNumPixels; i++)
    {
        //
        // Get the old color index and check whether it is
        // background or transparent
        //
        Int_t col = (*this)[i].GetFillColor();
        if (col==10 || col==16)
            continue;

        //
        // Search for the color index (level) in the color table
        //
        int idx;
        for (idx=0; idx<kItemsLegend; idx++)
            if (col==fColors[idx])
                break;

        //
        // Should not happen
        //
        if (idx==kItemsLegend)
        {
            cout << "MCamDisplay::SetPalette: Strange... FIXME!" << endl;
            continue;
        }

        //
        // Convert the old color index (level) into the new one
        //
        (*this)[i].SetFillColor(gStyle->GetColorPalette(idx));
    }

    //
    // Store the color palette used for a leter reverse lookup
    //
    for (int i=0; i<kItemsLegend; i++)
    {
        fColors[i] = gStyle->GetColorPalette(i);
        GetBox(i)->SetFillColor(fColors[i]);
    }
}

void MCamDisplay::SetPrettyPalette()
{
    SetPalette(1, 0);
}

void MCamDisplay::SetDeepBlueSeaPalette()
{
    SetPalette(51, 0);
}

void MCamDisplay::SetInvDeepBlueSeaPalette()
{
    SetPalette(52, 0);
}

void MCamDisplay::SetPalette()
{
    for (int i=0; i<kItemsLegend; i++)
        GetBox(i)->SetFillColor(fColors[i]);
}

void MCamDisplay::DrawPixelNumbers()
{
    for (int i=0; i<kItemsLegend; i++)
        fColors[i] = 16;

    if (!gPad)
        Draw();

    TText txt;
    txt.SetTextFont(122);
    txt.SetTextAlign(22);   // centered/centered

    for (UInt_t i=0; i<fNumPixels; i++)
    {
        TString num;
        num += i;

        const MHexagon &h = (*this)[i];
        TText *nt = txt.DrawText(h.GetX(), h.GetY(), num);
        nt->SetTextSize(0.3*h.GetD()/fGeomCam->GetMaxRadius());
    }
}

// ------------------------------------------------------------------------
//
// Call this function to fill the number of photo electron into the
// camera.
//
void MCamDisplay::FillPhotNum(const MCerPhotEvt &event)
{
    //
    // Reset pixel colors to default value
    //
    Reset();

    //
    //  if the autoscale is true, set the values for the range for
    //  each event
    //
    Float_t min = 0;
    Float_t max = 50;
    if (fAutoScale)
    {
        min = event.GetNumPhotonsMin(fGeomCam);
        max = event.GetNumPhotonsMax(fGeomCam);

        if (max==min)
            max = min +1;

        UpdateLegend(min, max);
    }

    //
    //   update the colors in the picture
    //
    const Int_t entries = event.GetNumPixels();

    for (Int_t i=0; i<entries; i++)
    {
        const MCerPhotPix &pix = event[i];

        if (!pix.IsPixelUsed())
            continue;

        SetPixColor(pix, i, min, max);
    }
}

// ------------------------------------------------------------------------
//
// Call this function to fill the number of photo electron into the
// camera.
//
void MCamDisplay::FillPedestals(const MPedestalCam &event)
{
    //
    // Reset pixel colors to default value
    //
    Reset();

    //
    //  if the autoscale is true, set the values for the range for
    //  each event
    //
    Float_t min = 0;
    Float_t max = 50;
    if (fAutoScale)
    {
        min = event.GetMeanMin(fGeomCam);
        max = event.GetMeanMax(fGeomCam);

        if (max==min)
            max = min +1;

        UpdateLegend(min, max);
    }

    //
    //   update the colors in the picture
    //
    const Int_t entries = event.GetSize();

    for (Int_t i=0; i<entries; i++)
        SetPixColorPedestal(event[i], i, min, max);
}

// ------------------------------------------------------------------------
//
// Call this function to fill the error of number of photo electron
// into the camera.
//
void MCamDisplay::FillErrorPhot(const MCerPhotEvt &event)
{
    //
    // Reset pixel colors to default value
    //
    Reset();

    //
    //  if the autoscale is true, set the values for the range for
    //  each event
    //
    Float_t min = 0;
    Float_t max = 50;
    if (fAutoScale)
    {
        min = event.GetErrorPhotMin(fGeomCam);
        max = event.GetErrorPhotMax(fGeomCam);

        if (max==min)
            max = min +1;

        UpdateLegend(min, max);
    }

    //
    //   update the colors in the picture
    //
    const Int_t entries = event.GetNumPixels();

    for (Int_t i=0; i<entries; i++)
    {
        const MCerPhotPix &pix = event[i];

        if (!pix.IsPixelUsed())
            continue;

        SetPixColorError(pix, i, min, max);
    }
}

// ------------------------------------------------------------------------
//
// Call this function to fill the ratio of the number of photons
// divided by its error
//
void MCamDisplay::FillRatio(const MCerPhotEvt &event)
{
    //
    // Reset pixel colors to default value
    //
    Reset();

    //
    //  if the autoscale is true, set the values for the range for
    //  each event
    //
    Float_t min = 0;
    Float_t max = 20;
    if (fAutoScale)
    {
        min = event.GetRatioMin();
        max = event.GetRatioMax();

        UpdateLegend(min, max);
    }

    //
    //   update the colors in the picture
    //
    const Int_t entries = event.GetNumPixels();

    for (Int_t i=0; i<entries; i++)
    {
        const MCerPhotPix &pix = event[i];

        if (!pix.IsPixelUsed())
            continue;

        SetPixColorRatio(pix, min, max);
    }
}

// ------------------------------------------------------------------------
//
// Fill the colors in respect to the cleaning levels
//
void MCamDisplay::FillLevels(const MCerPhotEvt &event, Float_t lvl1, Float_t lvl2)
{
    //
    // Reset pixel colors to default value
    //
    Reset();

    //
    //   update the colors in the picture
    //
    const Int_t entries = event.GetNumPixels();

    for (Int_t i=0; i<entries; i++)
    {
        const MCerPhotPix &pix = event[i];

        if (!pix.IsPixelUsed())
            continue;

        SetPixColorLevel(pix, lvl1, lvl2);
    }
}

// ------------------------------------------------------------------------
//
// Fill the colors in respect to the cleaning levels
//
void MCamDisplay::FillLevels(const MCerPhotEvt &event, const MImgCleanStd &clean)
{
    FillLevels(event, clean.GetCleanLvl1(), clean.GetCleanLvl2());
}

// ------------------------------------------------------------------------
//
// Show a reflector event. EMarkerStyle is defined in root/include/Gtypes.h
// To remove the photons from the display call FillRflEvent(NULL)
//
void MCamDisplay::ShowRflEvent(const MRflEvtData *event, EMarkerStyle ms)
{
    const Int_t num = event ? event->GetNumPhotons() : 0;

    fPhotons->ExpandCreate(num);
    if (num < 1)
        return;

    Int_t i=num-1;
    do
    {
        const MRflSinglePhoton &ph = event->GetPhoton(i);
        TMarker &m = (TMarker&)*fPhotons->UncheckedAt(i);
        m.SetX(ph.GetX());
        m.SetY(ph.GetY());
        m.SetMarkerStyle(ms);
    } while (i--);
}

// ------------------------------------------------------------------------
//
// Fill a reflector event. Sums all pixels in each pixel as the
// pixel contents.
//
// WARNING: Due to the estimation in DistanceToPrimitive and the
//          calculation in pixels instead of x, y this is only a
//          rough estimate.
//
void MCamDisplay::FillRflEvent(const MRflEvtData &event)
{
    //
    // reset pixel colors to background color
    //
    Reset();

    //
    // sum the photons content in each pixel
    //
    const Int_t entries = event.GetNumPhotons();

    TArrayF arr(fNumPixels);
    for (Int_t i=0; i<entries; i++)
    {
        const MRflSinglePhoton &ph = event.GetPhoton(i);

        UInt_t id;
        for (id=0; id<fNumPixels; id++)
        {
            if ((*this)[id].DistanceToPrimitive(ph.GetX(), ph.GetY())<0)
                break;
        }
        if (id==fNumPixels)
            continue;

        arr[id] += 1;
    }

    //
    // Scale with the area and determin maximum
    //
    Float_t max = 0;
    for (UInt_t id=0; id<fNumPixels; id++)
    {
        arr[id] *= fGeomCam->GetPixRatio(id);
        if (arr[id]>max)
            max = arr[id];
    }

    //
    // Update legend
    //
    if (fAutoScale)
        UpdateLegend(0, max==0 ? 1 : max);

    //
    // Set color of pixels
    //
    for (UInt_t id=0; id<fNumPixels; id++)
        if (arr[id]>0)
            (*this)[id].SetFillColor(GetColor(arr[id], 0, max));
}

// ------------------------------------------------------------------------
//
// Reset the all pixel colors to a default value
//
void MCamDisplay::Reset()
{
    for (UInt_t i=0; i<fNumPixels; i++)
        (*this)[i].SetFillColor(10);
} 

// ------------------------------------------------------------------------
//
//  Here we calculate the color index for the current value.
//  The color index is defined with the class TStyle and the
//  Color palette inside. We use the command gStyle->SetPalette(1,0)
//  for the display. So we have to convert the value "wert" into
//  a color index that fits the color palette.
//  The range of the color palette is defined by the values fMinPhe
//  and fMaxRange. Between this values we have 50 color index, starting
//  with 0 up to 49.
//
Int_t MCamDisplay::GetColor(Float_t val, Float_t min, Float_t max)
{
    //
    //   first treat the over- and under-flows
    //
    const Int_t maxcolidx = kItemsLegend-1;

    if (val >= max)
        return fColors[maxcolidx];

    if (val <= min)
        return fColors[0];

    //
    // calculate the color index
    //
    const Float_t ratio  = (val-min) / (max-min);
    const Int_t   colidx = (Int_t)(ratio*maxcolidx + .5);

    return fColors[colidx];
}

// ------------------------------------------------------------------------
//
//  Change the text on the legend according to the range of the Display
//
void MCamDisplay::UpdateLegend(Float_t minphe, Float_t maxphe)
{
    for (Int_t i=0; i<kItemsLegend; i+=3)
    {
        const Float_t val = minphe + (Float_t)i/kItemsLegend * (maxphe-minphe) ;

        TText &txt = *GetText(i);
        txt.SetText(txt.GetX(), txt.GetY(), Form(val<1e6?"%5.1f":"%5.1e", val));
    }
}

// ------------------------------------------------------------------------
//
// Save primitive as a C++ statement(s) on output stream out
//
void MCamDisplay::SavePrimitive(ofstream &out, Option_t *opt)
{
    cout << "MCamDisplay::SavePrimitive: Must be rewritten!" << endl;
    /*
    if (!gROOT->ClassSaved(TCanvas::Class()))
        fDrawingPad->SavePrimitive(out, opt);

    out << "   " << fDrawingPad->GetName() << "->SetWindowSize(";
    out << fDrawingPad->GetWw() << "," << fDrawingPad->GetWh() << ");" << endl;
    */
}

// ------------------------------------------------------------------------
//
// compute the distance of a point (px,py) to the Camera
// this functions needed for graphical primitives, that
// means without this function you are not able to interact
// with the graphical primitive with the mouse!!!
//
// All calcutations are running in pixel coordinates
//
Int_t MCamDisplay::DistancetoPrimitive(Int_t px, Int_t py)
{
    Int_t dist = 999999;

    for (UInt_t i=0; i<fNumPixels; i++)
    {
        Int_t d = (*fPixels)[i]->DistancetoPrimitive(px, py);

        if (d<dist)
            dist=d;
    }
    return dist==0?0:999999;
}

// ------------------------------------------------------------------------
//
// Execute a mouse event on the camera
//
/*
 void MCamDisplay::ExecuteEvent(Int_t event, Int_t px, Int_t py)
 {
 cout << "Execute Event Camera " << event << " @ " << px << " " << py << endl;
 }
 */


// ------------------------------------------------------------------------
//
// Function introduced  (31-01-03)
//
void MCamDisplay::SetPix(const Int_t pixnum, const Int_t color, Float_t min, Float_t max)
{ 
    (*this)[pixnum].SetFillColor(GetColor(color, min, max));
}

// ------------------------------------------------------------------------
//
// Returns string containing info about the object at position (px,py).
// Returned string will be re-used (lock in MT environment).
//
char *MCamDisplay::GetObjectInfo(Int_t px, Int_t py) const
{
    static char info[64];

    UInt_t i;
    for (i=0; i<fNumPixels; i++)
    {
        if ((*fPixels)[i]->DistancetoPrimitive(px, py)>0)
            continue;

        sprintf(info, "Pixel Id: %d", i);
        return info;
    }
    return TObject::GetObjectInfo(px, py);
}
