/*

  xmunipack - figure caption

  Copyright © 2019 F.Hroch (hroch@physics.muni.cz)

  This file is part of Munipack.

  Munipack is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  Munipack is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with Munipack.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "xmunipack.h"
#include "display.h"
#include "fits.h"
#include <wx/wx.h>
#include <map>

using namespace std;

#define OBJECT "Object"
#define FILTER "Filter"
#define CSPACE "Colourspace"
#define DATE "Date"
#define TIME "Time"
#define EXPTIME "Exposure"
#define COUNTS "Counts"
#define INTENSITY "Intensity"
#define ALPHA L"α"
#define DELTA L"δ"

static const char *cspace_label_XYZ[] = { "X","Y","Z" };
static const char *cspace_label_Lab[] = { "L","a","b" };

static wxString LABEL(wxString a)
{
  if( a != "" )
    return "<span fgcolor=\"gray\">"  + a +  "</span>";
  else
    return "";
}

MuniDisplayCaptionInfo::MuniDisplayCaptionInfo(wxWindow *w, MuniConfig *c):
  wxPanel(w,wxID_ANY), config(c), init(false)
{
  wxSizerFlags label_flags, value_flags;
  label_flags.Align(wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT).DoubleBorder(wxRIGHT);
  value_flags.Align(wxALIGN_CENTER_VERTICAL).Expand();

  wxStaticText *l;

  // Object
  label_object = new wxStaticText(this, wxID_ANY,"");
  label_object->SetLabelMarkup(LABEL(OBJECT));
  object = new wxStaticText(this, wxID_ANY, "Object");

  wxFlexGridSizer *objectsizer = new wxFlexGridSizer(2);
  objectsizer->Add(label_object,label_flags);
  objectsizer->Add(object,value_flags);

  // Exposure time
  l = new wxStaticText(this, wxID_ANY,"");
  l->SetLabelMarkup(LABEL(EXPTIME));
  exptime = new wxStaticText(this, wxID_ANY, "Exptime [s]");

  wxFlexGridSizer *filtersizer = new wxFlexGridSizer(2);
  filtersizer->Add(l,label_flags);
  filtersizer->Add(exptime,value_flags);

  // Filter; the filter is
  label_colour = new wxStaticText(this, wxID_ANY,"");
  label_colour->SetLabelMarkup(LABEL(FILTER));
  colour = new wxStaticText(this, wxID_ANY, "Filter");
  filtersizer->Add(label_colour,label_flags);
  filtersizer->Add(colour,value_flags);

  wxFlexGridSizer *datesizer = new wxFlexGridSizer(2);
  l = new wxStaticText(this, wxID_ANY,"");
  l->SetLabelMarkup(LABEL(DATE));
  date = new wxStaticText(this, wxID_ANY, "Date");
  datesizer->Add(l,label_flags);
  datesizer->Add(date,value_flags);

  l = new wxStaticText(this, wxID_ANY,"");
  l->SetLabelMarkup(LABEL(TIME));
  time = new wxStaticText(this, wxID_ANY, "Time");
  datesizer->Add(l,label_flags);
  datesizer->Add(time,value_flags);

  wxBoxSizer *topsizer = new wxBoxSizer(wxHORIZONTAL);
  topsizer->AddStretchSpacer(13);
  topsizer->Add(objectsizer,wxSizerFlags().Border());
  topsizer->AddStretchSpacer();
  topsizer->Add(filtersizer,wxSizerFlags().Border());
  topsizer->AddStretchSpacer();
  topsizer->Add(datesizer,wxSizerFlags().Border());
  topsizer->AddStretchSpacer(13);

  SetSizer(topsizer);

  Bind(wxEVT_IDLE,&MuniDisplayCaptionInfo::OnIdle,this);
}

void MuniDisplayCaptionInfo::SetArray(const FitsArray& a)
{
  wxASSERT(a.IsOk());
  array = a;
  init = true;
}

void MuniDisplayCaptionInfo::OnIdle(wxIdleEvent& event)
{
  /*
    All the info is filled during idle time.
    If FITS keyword for object is missing, the label is hidden.
    All other labels (not values) are visible, even if they are
    missing to inform user by subliminal way.
  */

  if( init ) {
    init = false;

    wxString obj(array.GetKey(config->fits_key_object));

    // to format of exposure time
    wxString expline(array.GetKey(config->fits_key_exptime));
    double e;
    if( expline.ToDouble(&e) ) {
      wxString line;
      if( e > 0.1 ) {
	if( abs(round(e) - e) < 0.05 )
	  line.Printf("%.0f s",e);
	else
	  line.Printf("%.1f s",e);
      }
      else
	line.Printf("1/%.0f s",1.0/e);
      expline = line;
    }

    // DATE-OBS split
    FitsTime ft(array.GetKey(config->fits_key_dateobs));

    // Filter or Colour-space
    wxString clabel;
    if( array.IsColour() ) {
      label_colour->SetLabelMarkup(LABEL(CSPACE));
      clabel = "CIE 1931 XYZ";
    }
    else
      clabel = array.GetKey(config->fits_key_filter);

    label_object->SetLabelMarkup(obj == "" ? "" : LABEL(OBJECT));
    object->SetLabel(obj);
    exptime->SetLabel(expline);
    colour->SetLabel(clabel);
    date->SetLabel(ft.Date());
    time->SetLabel(ft.Time());

    Layout();
  }
}

MuniDisplayCaptionMotion::MuniDisplayCaptionMotion(wxWindow *w, MuniConfig *c):
  wxPanel(w,wxID_ANY), config(c), cspace("XYZ"), xyz(0), lab(0),
  init(false), update(false), hascal(false), show_wcs(false)
{
  wxSizerFlags label_flags, value_flags, number_flags;
  label_flags.Align(wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT).DoubleBorder(wxRIGHT);
  value_flags.Align(wxALIGN_CENTER_VERTICAL).Expand();
  number_flags.Align(wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT);

  options = new wxButton(this,wxID_ANY,L"⚙",wxDefaultPosition,
			 wxDefaultSize,wxBU_EXACTFIT);

  wxStaticText *l;

  // A photometry quantity
  label_quantity = new wxStaticText(this, wxID_ANY,"");
  label_quantity->SetLabelMarkup(LABEL(COUNTS));
  quantity = new wxStaticText(this, wxID_ANY, "Counts");
  units_quantity = new wxStaticText(this, wxID_ANY,"");
  units_quantity->SetLabelMarkup(LABEL(""));

  quantitysizer = new wxFlexGridSizer(2);
  quantitysizer->Add(label_quantity,label_flags);
  quantitysizer->Add(quantity,value_flags);
  quantitysizer->AddStretchSpacer();
  quantitysizer->Add(units_quantity,value_flags);

  // colours, mutual with the quantity
  lab = new MuniDisplayCaptionColour(this,false);
  xyz = new MuniDisplayCaptionColour(this,true);

  // pixels coordinates
  pixsizer = new wxFlexGridSizer(2);

  l = new wxStaticText(this, wxID_ANY,"");
  l->SetLabelMarkup(LABEL("x"));
  xpix = new wxStaticText(this, wxID_ANY, "X");
  pixsizer->Add(l,label_flags);
  pixsizer->Add(xpix,number_flags);

  l = new wxStaticText(this, wxID_ANY,"");
  l->SetLabelMarkup(LABEL("y"));
  ypix = new wxStaticText(this, wxID_ANY, "Y");
  pixsizer->Add(l,label_flags);
  pixsizer->Add(ypix,number_flags);

  // spherical coordinates
  coosizer = new wxFlexGridSizer(2);

  l = new wxStaticText(this, wxID_ANY,"");
  l->SetLabelMarkup(LABEL(ALPHA));
  alpha = new wxStaticText(this, wxID_ANY, "Right Ascension");
  coosizer->Add(l,label_flags);
  coosizer->Add(alpha,number_flags);

  l = new wxStaticText(this, wxID_ANY,"");
  l->SetLabelMarkup(LABEL(DELTA));
  delta = new wxStaticText(this, wxID_ANY, "Declination");
  coosizer->Add(l,label_flags);
  coosizer->Add(delta,number_flags);

  // final packing
  wxBoxSizer *topsizer = new wxBoxSizer(wxHORIZONTAL);
  topsizer->Add(options,wxSizerFlags().Border().Centre());
  topsizer->AddStretchSpacer(13);
  topsizer->Add(lab,wxSizerFlags().Centre());
  topsizer->Add(xyz,wxSizerFlags().Centre());
  topsizer->Add(quantitysizer,wxSizerFlags().Border());
  topsizer->AddStretchSpacer(1);
  topsizer->Add(pixsizer,wxSizerFlags().Border());
  topsizer->AddStretchSpacer(1);
  topsizer->Add(coosizer,wxSizerFlags().Border());
  topsizer->AddStretchSpacer(13);
  SetSizer(topsizer);

  // colours are hidden by default
  lab->Show(false);
  xyz->Show(false);

  Bind(wxEVT_IDLE,&MuniDisplayCaptionMotion::OnIdle,this);
  Bind(wxEVT_COMMAND_BUTTON_CLICKED,&MuniDisplayCaptionMotion::OnPopOptions,
       this,options->GetId());
  Bind(EVT_SLEW,&MuniDisplayCaptionMotion::OnMouseMotion,this);
}

void MuniDisplayCaptionMotion::SetArray(const FitsArray& a)
{
  wxASSERT(a.IsOk());
  array = a;
  init = true;
}

void MuniDisplayCaptionMotion::OnIdle(wxIdleEvent& event)
{
  if( init ) {
    init = false;

    value = FitsValue(array,config->phsystemfile, config->fits_key_area,
		      config->fits_key_exptime, config->fits_key_filter);
    coords = FitsCoo(array);

    // spherical coordinates are visible when a calibration is in FITS
    show_wcs = coords.HasWCS();
    coords.SetType(show_wcs ? config->display_coo : COO_PIXEL);
    coosizer->Show(show_wcs);

    // photometry quantity
    value.SetType(config->display_val);
    hascal = value.HasPhcal();
    if( hascal ) {
      label_quantity->SetLabelMarkup(LABEL(value.GetName()));
      units_quantity->SetLabelMarkup(LABEL(value.GetUnit()));
    }

    // colours
    bool iscolour = array.IsColour();
    quantitysizer->Show(!iscolour);
    if( iscolour ) {
      lab->SetArray(array);
      xyz->SetArray(array);
      if( cspace == "CIELAB" ) {
	lab->Show(true);
	xyz->Show(false);
      }
      else {
	xyz->Show(true);
	lab->Show(false);
      }
    }
    else {
      lab->UnsetArray();
      xyz->UnsetArray();
      xyz->Show(false);
      lab->Show(false);
    }
    Layout();
  }

  if( update ) {
    update = false;

    if( quantity->IsShown() )
      quantity->SetLabel(value.Get_str(x,y));

    wxString xstr,ystr;
    coords.GetPix(x,y,xstr,ystr);
    xpix->SetLabel(xstr);
    ypix->SetLabel(ystr);

    if( show_wcs ) {
      wxString a,d;
      coords.GetCoo(x,y,a,d);
      alpha->SetLabel(a);
      delta->SetLabel(d);
    }

    Layout();
  }
}


void MuniDisplayCaptionMotion::OnPopOptions(wxCommandEvent& event)
{
  wxMenu popup;

  wxArrayString labels;
  wxString label;

  if( ! array.IsColour() ) {

    labels = FitsValue::Label_str();
    label = FitsValue::Label_str(value.GetType());

    for(size_t i = 1; i < labels.GetCount(); i++) { // counts are skipped
      wxMenuItem *item = popup.AppendRadioItem(wxID_ANY,labels[i]);
      long id = item->GetId();
      popup.Check(id,labels[i] == label);
      popup.Enable(id,value.HasPhcal());
      hash[id] = labels[i];
      Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniDisplayCaptionMotion::OnOptions,
	   this,id);
    }
  }
  else {

    labels.Empty();
    labels.Add("CIE 1931 XYZ");
    labels.Add("CIELAB");
    label = cspace;
    for(size_t i = 0; i < labels.GetCount(); i++) { // counts are skipped
      wxMenuItem *item = popup.AppendRadioItem(wxID_ANY,labels[i]);
      long id = item->GetId();
      popup.Check(id,labels[i] == label);
      hash[id] = labels[i];
      Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniDisplayCaptionMotion::OnOptions,
	   this,id);
    }
  }

  popup.AppendSeparator();

  labels = FitsCoo::Label_str();
  label = FitsCoo::Label_str(coords.GetType());

  for(size_t i = 1; i < labels.GetCount(); i++) { // pixels are skipped
    wxMenuItem *item = popup.AppendRadioItem(wxID_ANY,labels[i]);
    long id = item->GetId();
    popup.Check(id,labels[i] == label);
    hash[id] = labels[i];
    popup.Enable(id,coords.HasWCS());
    Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniDisplayCaptionMotion::OnOptions,
	 this,id);
  }

  PopupMenu(&popup);

  for(map<long,wxString>::const_iterator i = hash.begin(); i != hash.end(); ++i)
    Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniDisplayCaptionMotion::OnOptions,
	   this,i->first);
}

void MuniDisplayCaptionMotion::OnOptions(wxCommandEvent& event)
{
  wxString label = hash[event.GetId()];

  wxArrayString labels = FitsValue::Label_str();
  for(size_t i = 1; i < labels.GetCount(); i++)
    if( labels[i] == label ) {
      value.SetType(labels[i]);
      config->display_val = value.GetType();
      label_quantity->SetLabelMarkup(LABEL(value.GetName()));
      units_quantity->SetLabelMarkup(LABEL(value.GetUnit()));
      break;
    }

  labels.Empty();
  labels.Add("CIE 1931 XYZ");
  labels.Add("CIELAB");
  for(size_t i = 0; i < labels.GetCount(); i++)
    if( labels[i] == label ) {
      cspace = label;
      break;
    }


  labels = FitsCoo::Label_str();
  for(size_t i = 1; i < labels.GetCount(); i++)
    if( labels[i] == label ) {
      coords.SetType(labels[i]);
      config->display_coo = coords.GetType();
      break;
    }

  init = true;
  update = true;
  wxWakeUpIdle();
}

void MuniDisplayCaptionMotion::OnMouseMotion(MuniSlewEvent& event)
{
  update = true;
  x = event.x;
  y = event.y;
  if( xyz )
    wxQueueEvent(xyz,event.Clone());
  if( lab )
    wxQueueEvent(lab,event.Clone());
}

MuniDisplayCaption::MuniDisplayCaption(wxWindow *w, MuniConfig *c):
  wxPanel(w,wxID_ANY), config(c), timer(this), inside(false)
{
  info = new MuniDisplayCaptionInfo(this,config);
  motion = new MuniDisplayCaptionMotion(this,config);

  wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
  topsizer->Add(info,wxSizerFlags().Expand());
  topsizer->Add(motion,wxSizerFlags().Expand());
  SetSizer(topsizer);
  motion->Show(false);

  Bind(EVT_SLEW,&MuniDisplayCaption::OnMouseMotion,this);
  Bind(wxEVT_TIMER,&MuniDisplayCaption::OnTimer,this);
}

void MuniDisplayCaption::SetArray(const FitsArray& array)
{
  wxASSERT(array.IsOk());
  info->SetArray(array);
  motion->SetArray(array);
  rect = wxRect(0,0,array.GetWidth()-1,array.GetHeight()-1);
}


void MuniDisplayCaption::OnMouseMotion(MuniSlewEvent& event)
{
  //wxLogDebug("MuniDisplayCaption::OnMouseMotion %d %d",event.x,event.y);

  inside = rect.Contains(event.x,event.y);

  if( inside )
    wxQueueEvent(motion,event.Clone());

  if( motion->IsShown() && ! inside )
    timer.StartOnce(3000);

  if( info->IsShown() && inside ) {
    //    wxLogDebug("Hidding spanish inquisition...");
    info->Show(false);
    motion->Show(true);
    Layout();
  }
}

void MuniDisplayCaption::OnTimer(wxTimerEvent& event)
{
  //  wxLogDebug("Revealing spanish inquisition...");
  info->Show(true);
  motion->Show(false);
  Layout();
}

MuniDisplayCaptionColour::MuniDisplayCaptionColour(wxWindow *w, bool xyz):
  wxPanel(w,wxID_ANY), cie1931xyz(xyz), update(false)
{
  wxArrayString names;

  for(size_t i = 0; i < 3; i++)
    names.Add(cie1931xyz ? cspace_label_XYZ[i] : cspace_label_Lab[i]);

  wxSizerFlags label_flags, value_flags, number_flags;
  label_flags.Align(wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT).Border(wxRIGHT);
  value_flags.Align(wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT).Expand();
  number_flags.Align(wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT);

  wxStaticText *labels[3];
  for(size_t i = 0; i < 3; i++) {
    labels[i] = new wxStaticText(this, wxID_ANY,"");
    labels[i]->SetLabelMarkup(LABEL(names[i]));
    colours[i] = new wxStaticText(this, wxID_ANY, "0");
  }

  if( cie1931xyz ) {
    colours[0]->SetForegroundColour(wxColour(92,0,0));
    colours[1]->SetForegroundColour(wxColour(0,92,0));
    colours[2]->SetForegroundColour(wxColour(0,0,92));

    wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);
    for(size_t i = 0; i < 3; i++) {
      wxBoxSizer *s = new wxBoxSizer(wxHORIZONTAL);
      s->Add(labels[i],label_flags);
      s->Add(colours[i],number_flags);
      sizer->Add(s,wxSizerFlags().DoubleBorder());
    }
    SetSizer(sizer);
  }
  else { // Lab

    wxGridSizer *s = new wxGridSizer(2);
    s->Add(labels[0],label_flags);
    s->Add(colours[0],value_flags);

    wxFlexGridSizer *r = new wxFlexGridSizer(2);
    r->Add(labels[1],label_flags);
    r->Add(colours[1],number_flags);
    r->Add(labels[2],label_flags);
    r->Add(colours[2],number_flags);

    wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);
    sizer->Add(s,wxSizerFlags().Border(wxRIGHT));
    sizer->Add(r);
    SetSizer(sizer);
  }

  Bind(EVT_SLEW,&MuniDisplayCaptionColour::OnMouseMotion,this);
}

void MuniDisplayCaptionColour::SetArray(const FitsArray& a)
{
  wxASSERT(a.IsOk() && a.IsColour());
  array = a;
  Bind(wxEVT_IDLE,&MuniDisplayCaptionColour::OnIdle,this);
}

void MuniDisplayCaptionColour::UnsetArray()
{
  array = FitsArray();
  Unbind(wxEVT_IDLE,&MuniDisplayCaptionColour::OnIdle,this);
}


wxString MuniDisplayCaptionColour::print(float q)
{
  wxString a;
  if( abs(round(q) - q) < 0.1 )
    a.Printf("%.0f",q);
  else
    a.Printf("%.1f",q);
  return a;
}

void MuniDisplayCaptionColour::OnIdle(wxIdleEvent& event)
{
  if( update ) {
    update = false;

    if( cie1931xyz ) {
      for(size_t i = 0; i < 3; i++)
	colours[i]->SetLabel(print(array.Pixel(x,y,2-i)));
    }
    else {
      float X = array.Pixel(x,y,2);
      float Y = array.Pixel(x,y,1);
      float Z = array.Pixel(x,y,0);

      float L,a,b;
      FitsColor colour;
      float *c[3] = { &L, &a, &b };

      colour.XYZ_Lab(1,&X,&Y,&Z,&L,&a,&b);
      colours[0]->SetLabel(HumanFormat(L));
      for(size_t i = 1; i < 3; i++) {
	wxString a;
	a.Printf("% 5.1f",*c[i]);
	colours[i]->SetLabel(a);
      }
    }
    Layout();
  }
}

void MuniDisplayCaptionColour::OnMouseMotion(MuniSlewEvent& event)
{
  update = true && array.IsOk();
  x = event.x;
  y = event.y;
}
