/* ======================================================================== *\
!
! *
! * 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 <TText.h>
#include <TArrow.h>
#include <TStyle.h>
#include <TCanvas.h>
#include <TButton.h>
#include <TClonesArray.h>

#include "MHexagon.h"

#include "MGeomCam.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
//
MCamDisplay::MCamDisplay(MGeomCam *geom)
    : fAutoScale(kTRUE), fW(0), fH(0), fDrawingPad(NULL), fIsAllocated(kFALSE)
{
    fGeomCam = (MGeomCam*)geom; // FIXME: Clone doesn't work! (MGeomCam*)geom->Clone();

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

    //
    // Construct all hexagons. Use new-operator with placement
    //

    // 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.

    fPixels = new TClonesArray("MHexagon", fNumPixels);
    for (UInt_t i=0; i<fNumPixels; i++)
        new ((*fPixels)[i]) MHexagon((*geom)[i]);

    //
    // set the color palette for the TBox elements
    //
#if ROOT_VERSION_CODE < ROOT_VERSION(3,01,06)
    SetPalette(1, 0);
#else
    SetPalette(51, 0);
#endif

    //
    // set up the Legend
    //
    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->SetFillColor(fColors[i]);

        newtxt->SetTextSize(0.025);
        newtxt->SetTextAlign(12);
    }

    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*geom->GetConvMm2Deg()*10))/10;
    text += "°";
    text = text.Strip(TString::kLeading);
    fLegDegree = new TText(-fRange*.85, -fRange*.75, text);
    fLegRadius->SetTextSize(0.04);
    fLegDegree->SetTextSize(0.04);
}

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

    delete fPixels;
    delete fLegend;
    delete fLegText;

    delete fArrowX;
    delete fArrowY;

    delete fLegRadius;
    delete fLegDegree;

    // delete fGeomCam;

    // Not allocated by MCamDisplay or already deleted by the user
    if (!fIsAllocated || !gROOT->GetListOfCanvases()->FindObject(fDrawingPad))
        return;

    // If it is not already removed make sure that nothing of this object
    // maybe be deleted automatically by the canvas destructor
    if (!fDrawingPad->GetListOfPrimitives()->FindObject(this))
        return;

    fDrawingPad->RecursiveRemove(this);
    delete fDrawingPad;
}

inline void MCamDisplay::SetPixColor(const MCerPhotPix &pix, const Int_t i, Float_t min, Float_t max)
{
    //
    // 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 Int_t i, Float_t min, Float_t max)
{
    //
    // 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 Int_t i, Float_t min, Float_t max)
{
    //
    // 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]);
}

// ------------------------------------------------------------------------
//
// This is called at any time the canvas should get repainted.
// Here we maintain an aspect ratio of 5/4=1.15. This makes sure,
// that the camera image doesn't get distorted by resizing the canvas.
//
void MCamDisplay::Paint(Option_t *opt)
{
    const UInt_t w = (UInt_t)(gPad->GetWw()*gPad->GetAbsWNDC());
    const UInt_t h = (UInt_t)(gPad->GetWh()*gPad->GetAbsHNDC());

    //
    // Check for a change in width or height, and make sure, that the
    // first call also sets the range
    //
    if (w*fH == h*fW && fW && fH)
        return;

    //
    // Calculate aspect ratio (5/4=1.25 recommended)
    //
    const Double_t ratio = (Double_t)w/h;

    Float_t x;
    Float_t y;

    if (ratio>1.25)
    {
        x = (ratio*2-1)*fRange; 
        y = fRange;
    }
    else
    {
        x = fRange*1.5;
        y = fRange*1.25/ratio;
    }

    fH = h;
    fW = w;

    //
    // Set new range
    //
    gPad->Range(-fRange, -y, x, y);

    //
    // Make sure, that the correct aspect is always displayed also
    // if - by chance - there is not update for the pad after the
    // Paint function was called.
    //
    gPad->Update();
}

// ------------------------------------------------------------------------
//
//  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);

    if (fDrawingPad)
    {
        //
        // Set the colors of the legend
        //
        for (int i=0; i<kItemsLegend; i++)
        {
            Int_t col = GetBox(i)->GetFillColor();

            //
            // Make sure, that the legend is already colored
            //
            if (col==10 || col==22)
                continue;
            GetBox(i)->SetFillColor(gStyle->GetColorPalette(i));
        }

        //
        // 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==22)
                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));
        }

        //
        // Update the pad on the screen
        //
        fDrawingPad->Modified();
        fDrawingPad->Update();
    }

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

// ------------------------------------------------------------------------
//
// 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()

    if (fDrawingPad)
        return;

    //
    // if no canvas is yet existing to draw into, create a new one
    //
    if (!gPad)
    {
        fDrawingPad = new TCanvas("CamDisplay", "Magic Camera Display", 0, 0, 750, 600);
        fIsAllocated = kTRUE;
    }
    else
    {
        fDrawingPad = gPad;
        fIsAllocated = kFALSE;
    }

    //
    // Setup the correct environment
    //
    fDrawingPad->SetBorderMode(0);
    fDrawingPad->SetFillColor(22);

    //
    // Set the initial coordinate range
    //
    Paint();

    //
    // Create and draw the buttons which allows changing the
    // color palette of the display
    //
    TButton *but;
    char txt[100];
    sprintf(txt, "((MCamDisplay*)%p)->SetPalette(1,0);", this);
    but = new TButton("Pretty", txt, 0.01, 0.95, 0.15, 0.99);
    but->Draw();
    sprintf(txt, "((MCamDisplay*)%p)->SetPalette(51,0);", this);
    but = new TButton("Deap Sea", txt, 0.16, 0.95, 0.30, 0.99);
    but->Draw();
    sprintf(txt, "((MCamDisplay*)%p)->SetPalette(52,0);", this);
    but = new TButton("Blue Inv", txt, 0.31, 0.95, 0.45, 0.99);
    but->Draw();

    //
    // Draw all pixels of the camera
    //  (means apend all pixelobjects to the current pad)
    //
    for (UInt_t i=0; i<fNumPixels; i++)
    {
#if ROOT_VERSION_CODE > ROOT_VERSION(3,01,06)
        (*this)[i].SetBit(kNoContextMenu|kCannotPick);
#endif
        (*this)[i].SetFillColor(22);
        (*this)[i].Draw();
    }

    fArrowX->Draw();
    fArrowY->Draw();

    fLegRadius->Draw();
    fLegDegree->Draw();

    //
    // initialize and draw legend
    //
    const Float_t H = 0.9*fRange;
    const Float_t h = 2./kItemsLegend;

    const Float_t w = fRange/sqrt(fNumPixels);

    for (Int_t i=0; i<kItemsLegend; i++)
    {
        TBox *box = GetBox(i);
        box->SetX1(fRange);
        box->SetX2(fRange+w);
        box->SetY1(H*( i   *h - 1.));
        box->SetY2(H*((i+1)*h - 1.));
        box->SetFillColor(22);
#if ROOT_VERSION_CODE > ROOT_VERSION(3,01,06)
        box->SetBit(kNoContextMenu|kCannotPick);
#endif
        box->Draw();

        TText *txt = GetText(i);
        txt->SetX(fRange+1.5*w);
        txt->SetY(H*((i+0.5)*h - 1.));
#if ROOT_VERSION_CODE > ROOT_VERSION(3,01,06)
        txt->SetBit(kNoContextMenu|kCannotPick);
#endif
        txt->Draw();
    }

    //
    // Append this object, so that the aspect ratio is maintained
    // (Paint-function is called)
    // Add it here so that root will have it first in the internal list:
    // This means, that root 'sees' the whole camera instead of all the
    // single hexagons.
    //
    AppendPad(option);

    //fDrawingPad->SetEditable(kFALSE);
}

void MCamDisplay::DrawPixelNumbers()
{
    if (!fDrawingPad)
        Draw();

    fDrawingPad->cd();

    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 draw the number of photo electron into the
// camera.
//
void MCamDisplay::DrawPhotNum(const MCerPhotEvt *event)
{
    if (!event)
        return;

    Draw();

    fDrawingPad->cd();

    for (int i=0; i<kItemsLegend; i++)
        GetBox(i)->SetFillColor(fColors[i]);

    //
    // 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 < 20.)
            max = 20.;

        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);
    }

    //
    // Update display physically
    //
    fDrawingPad->Modified();
    fDrawingPad->Update();
}

// ------------------------------------------------------------------------
//
// Call this function to draw the number of photo electron into the
// camera.
//
void MCamDisplay::DrawPedestals(const MPedestalCam *event)
{
    if (!event)
        return;

    Draw();

    fDrawingPad->cd();

    for (int i=0; i<kItemsLegend; i++)
        GetBox(i)->SetFillColor(fColors[i]);

    //
    // 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 < 20.)
            max = 20.;

        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);

    //
    // Update display physically
    //
    fDrawingPad->Modified();
    fDrawingPad->Update();
}

// ------------------------------------------------------------------------
//
// Call this function to draw the error of number of photo electron
// into the camera.
//
void MCamDisplay::DrawErrorPhot(const MCerPhotEvt *event)
{
    if (!event)
        return;

    if (!fDrawingPad)
        Draw();

    fDrawingPad->cd();

    for (int i=0; i<kItemsLegend; i++)
        GetBox(i)->SetFillColor(fColors[i]);

    //
    // 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 < 20.)
            max = 20.;

        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);
    }

    //
    // Update display physically
    //
    fDrawingPad->Modified();
    fDrawingPad->Update();
}

// ------------------------------------------------------------------------
//
// Call this function to draw the ratio of the number of photons
// divided by its error
//
void MCamDisplay::DrawRatio(const MCerPhotEvt *event)
{
    if (!event)
        return;

    if (!fDrawingPad)
        Draw();

    fDrawingPad->cd();

    for (int i=0; i<kItemsLegend; i++)
        GetBox(i)->SetFillColor(fColors[i]);

    //
    // 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);
    }

    //
    // Update display physically
    //
    fDrawingPad->Modified();
    fDrawingPad->Update();
}

// ------------------------------------------------------------------------
//
// Draw the colors in respect to the cleaning levels
//
void MCamDisplay::DrawLevels(const MCerPhotEvt *event, Float_t lvl1, Float_t lvl2)
{
    if (!event)
        return;

    if (!fDrawingPad)
        Draw();

    fDrawingPad->cd();

    for (int i=0; i<kItemsLegend; i++)
        GetBox(i)->SetFillColor(fColors[i]);

    //
    // 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);
    }

    //
    // Update display physically
    //
    fDrawingPad->Modified();
    fDrawingPad->Update();
}

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

// ------------------------------------------------------------------------
//
// 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)
{
    char text[10];

    for (Int_t i=0; i<kItemsLegend; i+=3)
    {
        const Float_t val = minphe + (Float_t)i/kItemsLegend * (maxphe-minphe) ;

        sprintf(text, "%5.1f", val);

        TText &txt = *GetText(i);

        txt.SetText(txt.GetX(), txt.GetY(), text);
    }
}

// ------------------------------------------------------------------------
//
// Save primitive as a C++ statement(s) on output stream out
//
void MCamDisplay::SavePrimitive(ofstream &out, Option_t *opt)
{
    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;
 }
 */
