 /*

  xmunipack - display image

  Copyright © 2009-2013, 2017, 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 "tune.h"
#include <wx/wx.h>
#include <wx/mstream.h>
#include <cfloat>
#include <cmath>

using namespace std;



// -- MuniDisplay

MuniDisplay::MuniDisplay(wxWindow *w, MuniConfig *c):
  wxWindow(w,wxID_ANY),config(c),panel(0),caption(0),canvas(0),tune(0),
  magnifier(0),find(0),astrometry(0)
{
  wxASSERT(config);

  canvas = new MuniDisplayCanvas(this,c);
  panel = new MuniDisplayPanel(this,c);
  panel->Show(config->detail_show);

  caption = new MuniDisplayCaption(this,config);
  caption->Show(config->caption_show);

  magnifier = new MuniDisplayMagnifier(this,config);

  wxASSERT(canvas && panel && caption && magnifier);

  wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
  sizer->Add(canvas,wxSizerFlags(1).Expand());
  sizer->Add(caption,wxSizerFlags().Expand());

  wxBoxSizer *topsizer = new wxBoxSizer(wxHORIZONTAL);
  topsizer->Add(sizer,wxSizerFlags(1).Expand());
  topsizer->Add(panel,wxSizerFlags().Expand().Border(wxLEFT|wxRIGHT));
  SetSizerAndFit(topsizer);

  Bind(wxEVT_CLOSE_WINDOW,&MuniDisplay::OnClose,this,GetId());
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniDisplay::OnLeaveFullscreen,this,
       ID_MENU_FULLSCREEN);
  Bind(EVT_SLEW,&MuniDisplay::OnMouseMotion,this);
  Bind(EVT_TUNE,&MuniDisplay::OnTuneFine,this);
  Bind(EVT_ASTROMETRY,&MuniDisplayCanvas::OnAstrometry,
       static_cast<MuniDisplayCanvas *>(canvas));
  Bind(EVT_PHOTOMETRY,&MuniDisplayCanvas::OnPhotometry,
       static_cast<MuniDisplayCanvas *>(canvas));
  Bind(EVT_DRAW,&MuniDisplayCanvas::OnDraw,
       static_cast<MuniDisplayCanvas *>(canvas));
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniDisplayCanvas::OnValueType,
       static_cast<MuniDisplayCanvas *>(canvas),ID_VALTYPE);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniDisplayCanvas::OnCooType,
       static_cast<MuniDisplayCanvas *>(canvas),ID_COOTYPE);
  Bind(EVT_CLICK,&MuniDisplay::OnClick,this);
  Bind(EVT_RENDER,&MuniDisplay::OnRenderFinish,this);

  //  Bind(EVT_CONFIG_UPDATED,&MuniDisplayCanvas::OnConfigUpdated,
  //       static_cast<MuniDisplayCanvas *>(canvas));
}

void MuniDisplay::OnClose(wxCloseEvent& event)
{
  //  wxLogDebug("MuniDisplay::OnClose %d %d %d %d",magnifier->IsShown(),
  //	     event.GetId(),magnifier->GetId(),GetId());

  wxASSERT(magnifier);
  config->magnifier_size = magnifier->GetSize();
  config->magnifier_scale =
    static_cast<MuniDisplayMagnifier *>(magnifier)->GetScale();
  magnifier->Destroy();
  magnifier = 0;

  if( ! event.CanVeto() )
    Destroy();
}

bool MuniDisplay::SetHdu(const FitsHdu& hdu, const wxImage& icon)
{
  array = FitsArray(hdu);
  thumb = icon;

  wxASSERT(array.IsOk() && icon.IsOk());
  if( ! (array.IsOk() && icon.IsOk()) ) return false;

  wxASSERT(static_cast<MuniDisplayCanvas *>(canvas));
  wxASSERT(static_cast<MuniDisplayPanel *>(panel));
  wxASSERT(static_cast<MuniDisplayCaption *>(caption));
  wxASSERT(static_cast<MuniDisplayMagnifier *>(magnifier));

  static_cast<MuniDisplayPanel *>(panel)->SetArray(array);
  static_cast<MuniDisplayCanvas *>(canvas)->SetHdu(array,icon);
  static_cast<MuniDisplayCaption *>(caption)->SetArray(array);
  static_cast<MuniDisplayMagnifier *>(magnifier)->UnsetImage();

  return true;
}

void MuniDisplay::SetInitShrink(int shrink)
{
  wxASSERT(static_cast<MuniDisplayCanvas *>(canvas));
  static_cast<MuniDisplayCanvas *>(canvas)->SetInitShrink(shrink);
}

void MuniDisplay::InvokeRendering()
{
  wxASSERT(static_cast<MuniDisplayCanvas *>(canvas));
  static_cast<MuniDisplayCanvas *>(canvas)->InvokeRendering();
}

void MuniDisplay::StopRendering()
{
  wxASSERT(static_cast<MuniDisplayCanvas *>(canvas));
  static_cast<MuniDisplayCanvas *>(canvas)->StopRendering();
}

FitsTone MuniDisplay::GetTone() const
{
  wxASSERT(static_cast<MuniDisplayCanvas *>(canvas));
  return static_cast<MuniDisplayCanvas *>(canvas)->GetTone();
}

FitsItt MuniDisplay::GetItt() const
{
  wxASSERT(static_cast<MuniDisplayCanvas *>(canvas));
  return static_cast<MuniDisplayCanvas *>(canvas)->GetItt();
}

FitsColor MuniDisplay::GetColor() const
{
  wxASSERT(static_cast<MuniDisplayCanvas *>(canvas));
  return static_cast<MuniDisplayCanvas *>(canvas)->GetColor();
}

FitsPalette MuniDisplay::GetPalette() const
{
  wxASSERT(static_cast<MuniDisplayCanvas *>(canvas));
  return static_cast<MuniDisplayCanvas *>(canvas)->GetPalette();
}

wxImage MuniDisplay::GetImage()
{
  wxASSERT(static_cast<MuniDisplayCanvas *>(canvas));
  return static_cast<MuniDisplayCanvas *>(canvas)->GetImage();
}

void MuniDisplay::ShowPanel(bool show)
{
  wxASSERT(panel);
  panel->Show(show);
  Layout();
}

void MuniDisplay::ShowCaption(bool show)
{
  wxASSERT(caption);
  caption->Show(show);
  Layout();
}

bool MuniDisplay::IsMagnifierShown() const
{
  wxASSERT(magnifier);
  return magnifier->IsShown();
}


void MuniDisplay::ShowMagnifier(bool show)
{
  //  wxLogDebug("MuniDisplay::ShowMagnifier");

  wxASSERT(magnifier);
  if( show ) {
    wxRect rect = GetParent()->GetScreenRect();
    magnifier->SetPosition(rect.GetTopRight());
    magnifier->ShowWithoutActivating();
  }
  else
    magnifier->Hide();
}

void MuniDisplay::OnMouseMotion(MuniSlewEvent& event)
{
  wxASSERT(caption);
  wxQueueEvent(caption,event.Clone());

  if( magnifier && magnifier->IsShown() )
    wxQueueEvent(magnifier,event.Clone());
}

void MuniDisplay::OnClick(MuniClickEvent& event)
{
  if( find ) {
    find->SetPoint(event.x,event.y);
  }

  wxQueueEvent(GetParent(),event.Clone());
}

void MuniDisplay::OnLeaveFullscreen(wxCommandEvent& event)
{
  wxCommandEvent e(wxEVT_COMMAND_MENU_SELECTED,ID_FULLSCREEN);
  wxQueueEvent(GetParent(),e.Clone());
}

/*
void MuniDisplay::SetOverlay(wxInputStream& istream)
{
  wxASSERT(canvas);

  MuniSvg svg(istream);
  static_cast<MuniDisplayCanvas *>(canvas)->SetOverlay(svg.GetDrawObjects());
}
*/

void MuniDisplay::SetStars(FitsTable& table)
{
  wxASSERT(canvas);

  wxMemoryOutputStream ostream;
  table.GetStarChart(ostream);

  wxMemoryInputStream istream(ostream);
  //  MuniSvg svg(istream);
  //  static_cast<MuniDisplayCanvas *>(canvas)->AddOverlay(svg.GetDrawObjects());
}

/*
vector<MuniDrawBase *> MuniSvg::GetDrawObjects() const
{
  vector<MuniDrawBase *> objects;

  const wxXmlNode *node = GetRoot()->GetChildren();
  while(node) {

    if( node->GetName() == "circle" ) {

      double x;
      float cx=0.0,cy=0.0,r=0.0;
      wxString a;
      wxXmlAttribute *prop = node->GetAttributes();
      while(prop) {

	if( prop->GetName() == "cx" ) {
	  a = prop->GetValue();
	  if( a.ToDouble(&x) )
	    cx = x;
	}
	else if( prop->GetName() == "cy" ) {
	  a = prop->GetValue();
	  if( a.ToDouble(&x) )
	    cy = x;
	}
	else if( prop->GetName() == "r" ) {
	  a = prop->GetValue();
	  if( a.ToDouble(&x) )
	    r = x;
	}

	prop = prop->GetNext();
      }

      objects.push_back(new MuniDrawCircle(cx,cy,r));
    }

    node = node->GetNext();
  }

  return objects;
}
*/

void MuniDisplay::ShowGrid(bool show)
{
  wxASSERT(canvas);

  double x0, y0, a0, d0, scale, phi, reflex;
  const int ntics = 25;
  double tics[25] = { 90.0, 60.0, 30.0, 10.0, 5.0, 2.0, 1.0, 0.5, 0.2, 0.1,
		      0.05, 0.02, 0.01,
		    0.005, 0.002, 0.001, 0.0005, 0.0002, 0.0001, 5e-5, 2e-5,
		      1e-5, 5e-6, 2e-6, 1e-6};

  if( show && array.GetWCS(x0,y0,a0,d0,scale,phi,reflex) ) {
    vector<wxObject *> objects;

    wxColour bfont(91,95,255);
    wxColour b(153,153,255,128);
    //    wxColour b(255,255,255,128);
    wxPen pen(b,1/*,wxPENSTYLE_DOT*/);
    //    pen.SetStyle(wxPENSTYLE_DOT);
    objects.push_back(new MuniDrawPen(pen));
    //    objects.push_back(new MuniDrawLine(100.0,0.0,200.0,array.Naxes(1)));

    //    objects.push_back(new MuniDrawFont(*wxNORMAL_FONT,bfont));
    //    objects.push_back(new MuniDrawText(200.0,-20.0,"Right Ascension"));
    //    objects.push_back(new MuniDrawText(-20.0,50.0,1.57,"Declination"));


    if( fabs(phi) < 45.0 ) {

      // declination
      FitsProjection p("",a0,d0,x0,y0,scale,phi,reflex);
      //      wxLogDebug("%f %f %f %f %f %f",a0,d0,x0,y0,scale,phi);

      double amin, amax, dmin, dmax, atic, dtic;
      p.xy2ad(x0,0.0,amin,dmin);
      p.xy2ad(x0,array.Naxes(1),amax,dmax);

      //      wxLogDebug("%f %f %f %f",amin,amax,dmin,dmax);

      double d = fabs(dmax - dmin);
      for(int i = 0; i < ntics; i++)
	if( d > tics[i] ) {
	  dtic = tics[i];
	  break;
	}

      atic = dtic;

      double dstart = int(d0/dtic)*dtic;
      double astart = int(a0/atic)*atic;
      //      wxLogDebug("%f %f %f %f",atic,dtic,dstart,astart);

      int n = int(wxMax(dmax - d0,d0 - dmin)/dtic) + 1;
      vector<double> decs;
      for(int i = -n; i <= n; i++) {
	double d = dstart + i*dtic;
	if( dmin <= d && d <= dmax ) {
	  decs.push_back(d);
	  //	  wxLogDebug("%f",d);
	}
      }

      p.xy2ad(0.0,y0,amax,dmin);
      p.xy2ad(array.Naxes(0),y0,amin,dmax);

      n = int(wxMax(amax - a0,a0 - amin)/atic) + 1;
      vector<double> ras;
      for(int i = -n; i <= n; i++) {
	double a = astart + i*atic;
	if( amin <= a && a <= amax ) {
	  ras.push_back(a);
	  //	  wxLogDebug("%f",a);
	}
      }


      for(size_t i = 0; i < decs.size() && ras.size() > 0; i++) {

	double x1,y1,x2,y2,x,y;
	p.ad2xy(ras[0],decs[i],x1,y1);
	p.ad2xy(ras[0]-atic,decs[i],x,y);
	double r = fabs(array.Naxes(0)-x1)/(fabs(x) - fabs(x1));
	p.ad2xy(ras[0]-r*atic,decs[i],x1,y1);
	//	wxLogDebug("%f %f %f %f",a0+10.0*atic,decs[i],x1,y1);
	p.ad2xy(ras[ras.size()-1],decs[i],x,y);
	p.ad2xy(ras[ras.size()-1]+atic,decs[i],x2,y2);
	r = fabs(x)/(fabs(x) + fabs(x2));
	//	wxLogDebug("%f %f %f %f %f",x2,y2,x,y,r);
	p.ad2xy(ras[ras.size()-1]+r*atic,decs[i],x2,y2);
	//	p.ad2xy(a0-1.0*atic,decs[i],x2,y2);
	objects.push_back(new MuniDrawLine(x1,y1,x2,y2));
	//	wxLogDebug("%f %f %f %f",x1,y1,x2,y2);
      }

      for(size_t i = 0; i < ras.size() && decs.size() > 0; i++) {

	double x1,y1,x2,y2,x,y;
	p.ad2xy(ras[i],decs[0],x1,y1);
	p.ad2xy(ras[i],decs[0]-dtic,x,y);
	double r = fabs(y1)/(fabs(y1) + fabs(y));
	p.ad2xy(ras[i],decs[0]-r*dtic,x1,y1);
	//	wxLogDebug("%f %f %f %f",a0+10.0*atic,decs[i],x1,y1);
	p.ad2xy(ras[i],decs[decs.size()-1],x2,y2);
	p.ad2xy(ras[i],decs[decs.size()-1]+dtic,x,y);
	r = fabs(y2-array.Naxes(1))/(fabs(y) - fabs(y2));
	p.ad2xy(ras[i],decs[decs.size()-1]+r*dtic,x2,y2);
	objects.push_back(new MuniDrawLine(x1,y1,x2,y2));
	//	wxLogDebug("%f %f %f %f",x1,y1,x2,y2);
      }

      objects.push_back(new MuniDrawFont(*wxNORMAL_FONT,bfont));
      objects.push_back(new MuniDrawText(array.Naxes(0)/2.0,-20.0,"Right Ascension"));
      objects.push_back(new MuniDrawText(-40.0,array.Naxes(1)/2.0,1.57,"Declination"));

      wxString fmt;
      int m = rint(log10(dtic));
      if( m < 0 ) {
	wxString a;
	a.Printf("%d",abs(m));
	fmt = wxString(L"%." + a + L"f°");
      }
      else
	fmt = wxString(L"%.0f°");

      for(size_t i = 0; i < ras.size() && decs.size() > 0; i++) {
	double x,y;
	p.ad2xy(ras[i],decs[0]-dtic,x,y);
	wxString a;
	a.Printf(fmt,ras[i]);
	objects.push_back(new MuniDrawText(x,-1.0,a));
      }

      for(size_t i = 0; i < decs.size() && ras.size() > 0; i++) {
	double x,y;
	p.ad2xy(ras[ras.size()-1]+atic,decs[i],x,y);
	wxString a;
	a.Printf(fmt,decs[i]);
	objects.push_back(new MuniDrawText(-1.0,y,1.57,a));
      }

    }

    MuniLayer layer(ID_GRID,objects);
    //    layer.objects = objects;
    //    static_cast<MuniDisplayCanvas *>(canvas)->ShowGrid(layer);
    static_cast<MuniDisplayCanvas *>(canvas)->AddLayer(layer);
  }
  else
    //    static_cast<MuniDisplayCanvas *>(canvas)->HideGrid();
    static_cast<MuniDisplayCanvas *>(canvas)->RemoveLayers(ID_GRID);
}

void MuniDisplay::ShowSources(bool show, const FitsFile& fits)
{
  wxASSERT(canvas);

  if( show ) {

    bool apext = false;
    for(size_t i = 0; i < fits.HduCount(); i++) {
      if( fits.Hdu(i).GetExtname() == APEREXTNAME )
	apext = true;
    }

    for(size_t i = 0; i < fits.HduCount(); i++) {
      if( fits.Hdu(i).GetExtname() == APEREXTNAME ||
	  fits.Hdu(i).GetExtname() == PHCALNAME ||
         (fits.Hdu(i).GetExtname() == FINDEXTNAME && !apext) ) {
	FitsTable table(fits.Hdu(i));
	const double *x = table.GetColumn(FITS_LABEL_X).GetCol_double();
	const double *y = table.GetColumn(FITS_LABEL_Y).GetCol_double();
	double *f = 0;
	if( fits.Hdu(i).GetExtname() == APEREXTNAME )
	  f = (double *)table.GetColumn(FITS_LABEL_APCOUNT "1").GetCol_double();
	else if( fits.Hdu(i).GetExtname() == PHCALNAME )
	  f = (double *) table.GetColumn(FITS_COL_PHOTONS).GetCol_double();

	vector<double> xcoo,ycoo,flux;
	for(long i = 0; i < table.Nrows(); i++) {
	  xcoo.push_back(x[i]);
	  ycoo.push_back(y[i]);
	  if( f )
	    flux.push_back(f[i]);
	  else
	    flux.push_back(-1.0);
	}

	MuniStarLayer layer;
	if( fits.Hdu(i).GetExtname() == APEREXTNAME )
	  layer.SetHWHM(fits.Hdu(i).GetKeyDouble("HWHM"));
	layer.DrawObjects(xcoo,ycoo,flux);
	static_cast<MuniDisplayCanvas *>(canvas)->AddLayer(layer.GetLayer());
      }
    }
  }
  else
    static_cast<MuniDisplayCanvas *>(canvas)->RemoveLayers(ID_PHOTOMETRY);
}


bool MuniDisplay::IsTuneShown() const
{
  return tune;
}

void MuniDisplay::ShowTune(bool show)
{
  if( ! tune && show) {

    wxRect rect = GetParent()->GetScreenRect();
    wxPoint point = rect.GetTopLeft();

    if( array.IsColour() )
      tune = new MuniTune(this,wxID_ANY,point,wxDefaultSize,
			  config->icon_size,array,GetTone(),GetItt(),
			  GetColor());
    else
      tune = new MuniTune(this,wxID_ANY,point,wxDefaultSize,
			  config->icon_size,array,GetTone(),GetItt(),
			  GetPalette());

    Bind(wxEVT_CLOSE_WINDOW,&MuniDisplay::OnCloseTune,this,tune->GetId());

    tune->Show(true);
  }
  else if ( tune && ! show ) {
    tune->Destroy();
    tune = 0;
  }
}

void MuniDisplay::OnCloseTune(wxCloseEvent& event)
{
  Unbind(wxEVT_CLOSE_WINDOW,&MuniDisplay::OnCloseTune,this,tune->GetId());
  tune->Destroy();
  tune = 0;
}

void MuniDisplay::OnFind(const wxString& fitsname, const FitsHdu& hdu)
{
  wxASSERT(find == 0);

  find = new MuniFind(this,config,fitsname,hdu);
  find->Show();
  Bind(EVT_FINISH_DIALOG,&MuniDisplay::OnCloseFind,this,find->GetId());
}

void MuniDisplay::OnCloseFind(wxCommandEvent& event)
{
  wxLogDebug("MuniDisplay::OnCloseFind");
  wxASSERT(find && static_cast<MuniView *>(GetParent()));

  /*
  int code = event.GetInt();
  wxString file = event.GetString();
  */

  Unbind(EVT_FINISH_DIALOG,&MuniDisplay::OnCloseFind,this,find->GetId());
  find = 0;
}


void MuniDisplay::Calibrate(const wxString& file)
{
  // check availability of write access
  wxFileName fn(file);
  if( ! fn.IsFileWritable() ) {
    wxLogWarning("File `"+file+"' is not writtable. Results of calibration should not be saved.");
  }

  MuniCalibrate calibrate(this,config,file);
  if( calibrate.ShowModal() == wxID_OK ) {

    /*
    wxString cfile = calibrate.GetResult();

    FitsFile fits(cfile);

    if( fits.IsOk() )
      for(size_t i = 0; i < fits.HduCount(); i++)
	if( fits.Hdu(i).GetKey("EXTNAME").Find("MUNIPACK") != wxNOT_FOUND ) {
	  FitsTable t(fits.Hdu(i));
	  SetStars(t);
	}

    wxRemoveFile(cfile);
    */

    wxCommandEvent e(EVT_FILELOAD,GetId());
    e.SetEventObject(GetParent());
    e.SetString(file);
    wxQueueEvent(GetParent(),e.Clone());


  }
}

void MuniDisplay::Astrometry(const wxString& file, const FitsTable& t)
{
  if( astrometry ) return;

  astrometry = new MuniAstrometry(this,config);
  astrometry->SetFile(file,array);
  astrometry->SetDetectedSources(t);
  astrometry->Show();

  Bind(EVT_FINISH_DIALOG,&MuniDisplay::OnCloseAstrometry,this,astrometry->GetId());

}

void MuniDisplay::OnCloseAstrometry(wxCommandEvent& event)
{
  wxASSERT(astrometry && static_cast<MuniView *>(GetParent()));

  int code = event.GetInt();
  wxString file = event.GetString();

  //wxLogDebug("Return code= %d %d %d %d %d",code,wxID_APPLY,code==wxID_APPLY,astrometry->GetAffirmativeId(),wxID_CANCEL);

  Unbind(EVT_FINISH_DIALOG,&MuniDisplay::OnCloseAstrometry,this,astrometry->GetId());
  //  astrometry->Destroy();
  astrometry = 0;

  if( code == wxID_APPLY )
    static_cast<MuniView *>(GetParent())->LoadFileBackup(file,file+"~");

}

wxSize MuniDisplay::GetCanvasSize() const
{
  wxASSERT(canvas);
  return canvas->GetSize();
}

void MuniDisplay::OnTuneFine(MuniTuneEvent& e)
{
  wxASSERT(canvas);
  wxQueueEvent(canvas,e.Clone());

  wxASSERT(magnifier);
  static_cast<MuniDisplayMagnifier *>(magnifier)->UnsetImage();
}
void MuniDisplay::OnRenderFinish(MuniRenderEvent& event)
{
  //  wxLogDebug("MuniDisplay::OnRenderFinish %d ",
  //	     event.GetId()==ID_RENDER_SHRINK);

  wxASSERT(magnifier);

  if( event.completed ) {
    MuniDisplayCanvas *c = static_cast<MuniDisplayCanvas *>(canvas);
    wxASSERT(c);
    static_cast<MuniDisplayMagnifier *>(magnifier)->SetImage(c->GetImage());
  }
}


/*

   HOW to create a throbbler

   - download glunarclock source package:
        http://glunarclock.sourceforge.net/

   - separate the long image sequence and resize images to single exposures:

       for A in `seq 0 55`; do
          P=$((A*48));
          X=$(printf "%02d" $A);
          convert moon_56frames.png -crop 48x48+$P+1 -resize 32x32 m_$X.png;
       done

   - animate:

       convert -delay 10  +repage m_*.png throbbler.gif


*/
