#include "MCamDisplay.h"

#include <math.h>
#include <fstream.h>

#include <TClonesArray.h>
#include <TCanvas.h>
#include <TStyle.h>
#include <TBox.h>
#include <TText.h>

#include "MHexagon.h"
#include "MGeomCam.h"

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

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

ClassImp(MCamDisplay);

// ------------------------------------------------------------------------
//
//  default constructor
//
MCamDisplay::MCamDisplay(MGeomCam *geom)
    : fAutoScale(kTRUE), fMinPhe(-2), fMaxPhe(50), fW(0), fH(0), fDrawingPad(NULL)
{
    //
    //  create the hexagons of the display
    //
    fNumPixels = geom->GetNumPixels();
    fRange     = geom->GetMaxRadius();

    //
    // Construct all hexagons. Use new-operator with placement
    //
    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
    //
    gStyle->SetPalette(1, 0);

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

        const Float_t lvl = 50. / kItemsLegend * i;

        newbox->SetFillColor(GetColor(lvl));

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

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

inline void MCamDisplay::SetPixColor(const MCerPhotPix &pix)
{
    (*this)[pix.GetPixId()].SetFillColor(GetColor(pix.GetNumPhotons()));
}

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

// ------------------------------------------------------------------------
//
// 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)
{
    //
    // 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);
    else
        fDrawingPad = gPad;

    fDrawingPad->SetBorderMode(0);

    //
    // Append this object, so that the aspect ratio is maintained
    // (Paint-function is called)
    //
    AppendPad(option);

    //
    // Setup the correct environment
    //
    gStyle->SetPalette(1, 0);

    gPad->SetFillColor(22);

    //
    // Draw all pixels of the camera
    //  (means apend all pixelobjects to the current pad)
    //
    for (UInt_t i=0; i<fNumPixels; i++)
        (*this)[i].Draw();

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

        TText *txt = GetText(i);
        txt->SetX(fRange+1.5*w);
        txt->SetY(H*((i+0.5)*h - 1.));
        txt->Draw();
    }

    //fDrawingPad->SetEditable(kFALSE);
}

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

    fDrawingPad->cd();

    //
    // Reset pixel colors to default value
    //
    Reset();

    //
    //  if the autoscale is true, set the values for the range for
    //  each event
    //
    if (fAutoScale)
    {
        fMinPhe = event->GetNumPhotonsMin();
        fMaxPhe = event->GetNumPhotonsMax();

        if (fMaxPhe < 20.)
            fMaxPhe = 20.;

        UpdateLegend();
    }

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

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

// ------------------------------------------------------------------------
//
// 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)
{
    //
    //   first treat the over- and under-flows
    //
    const Float_t maxcolidx = 49.0;

    if (val >= fMaxPhe)
        return gStyle->GetColorPalette(maxcolidx);

    if (val <= fMinPhe)
        return gStyle->GetColorPalette(0);

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

    return gStyle->GetColorPalette(colidx);
}

// ------------------------------------------------------------------------
//
//    change the text on the legend according to the range of the
//    Display
//
void MCamDisplay::UpdateLegend()
{
    char text[10];

    for (Int_t i=0; i<kItemsLegend; i++)
    {
        const Float_t val = fMinPhe + (Float_t)i/kItemsLegend * (fMaxPhe-fMinPhe) ;

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