/*

  xmunipack - display image

  Copyright © 2009-2013, 2017 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/dcbuffer.h>
#include <wx/brush.h>
#include <wx/graphics.h>
#include <wx/renderer.h>
#include <wx/thread.h>
#include <wx/statline.h>
#include <wx/animate.h>
#include <wx/xml/xml.h>
#include <wx/mstream.h>
#include <wx/scrolwin.h>
#include <cfloat>
#include <cmath>

using namespace std;



// -- MuniDisplay

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

  canvas = new MuniDisplayCanvas(this,c);
  panel = new MuniDisplayPanel(this,c);

  wxASSERT(canvas && panel);

  static_cast<MuniDisplayCanvas *>(canvas)->SetHandler(panel);

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

  Bind(wxEVT_CLOSE_WINDOW,&MuniDisplay::OnClose,this);
  Bind(wxEVT_ENTER_WINDOW,&MuniDisplay::OnEnterWin,this);
  //  Bind(wxEVT_LEAVE_WINDOW,&MuniDisplay::OnLeaveWin,this);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniDisplay::OnLeaveFullscreen,this,
       ID_MENU_FULLSCREEN);
  Bind(EVT_TUNE,&MuniDisplayCanvas::OnTuneFine,
       static_cast<MuniDisplayCanvas *>(canvas));
  Bind(EVT_ASTROMETRY,&MuniDisplayCanvas::OnAstrometry,
       static_cast<MuniDisplayCanvas *>(canvas));
  Bind(EVT_PHOTOMETRY,&MuniDisplayCanvas::OnPhotometry,
       static_cast<MuniDisplayCanvas *>(canvas));
  Bind(EVT_CONFIG_UPDATED,&MuniDisplayCanvas::OnConfigUpdated,
       static_cast<MuniDisplayCanvas *>(canvas));
}

void MuniDisplay::OnClose(wxCloseEvent& event)
{
  wxLogDebug("MuniDisplay::OnClose");
  event.Skip();
}

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

  // histogram + itt
  FitsArrayStat stat(array.IsColor() ? array.Plane(1) : array);
  double xmin = stat.Med() - 5.0*stat.Mad();
  double xmax = stat.Med() + 7.0*stat.Mad();
  hist = FitsHisto(array,xmin,xmax);

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

  wxASSERT(static_cast<MuniDisplayCanvas *>(canvas));
  wxASSERT(static_cast<MuniDisplayPanel *>(panel));

  static_cast<MuniDisplayPanel *>(panel)->SetHdu(array,hist);
  static_cast<MuniDisplayCanvas *>(canvas)->SetHdu(array,icon,stat);

  return true;
}


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

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

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

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

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

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

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

void MuniDisplay::OnEnterWin(wxMouseEvent& event)
{
  canvas->SetFocus();
  //  wxQueueEvent(GetParent(),event.Clone());
}

void MuniDisplay::OnLeaveWin(wxMouseEvent& event)
{
  wxQueueEvent(GetParent(),event.Clone());
}

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

/*
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 ) {
    for(size_t i = 0; i < fits.HduCount(); i++) {
      if( fits.Hdu(i).GetExtname() == APEREXTNAME ||
	  fits.Hdu(i).GetExtname() == PHCALNAME ) {
	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]);
	}

	MuniPhotometryLayer layer;
	layer.DrawObjects(xcoo,ycoo,flux);
	static_cast<MuniDisplayCanvas *>(canvas)->AddLayer(layer.GetLayer());
      }
    }
  }
  else
    static_cast<MuniDisplayCanvas *>(canvas)->RemoveLayers(ID_PHOTOMETRY);
}


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

    if( array.IsColor() )
      tune = new MuniTune(this,ID_TUNE,GetPosition(),wxDefaultSize,config,array,GetItt(),GetColor());
    else
      tune = new MuniTune(this,ID_TUNE,GetPosition(),wxDefaultSize,config,array,GetItt(),GetPalette());

    tune->Show(true);

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


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

void MuniDisplay::Photometry(const wxString& file)
{
  MuniPhotometry photometry(this,config,file);
  if( photometry.ShowModal() == wxID_OK )
    static_cast<MuniView *>(GetParent())->LoadFileBackup(file,photometry.GetBackup());
}

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+"~");

}

// void MuniDisplay::OnClosePhotometry(wxCloseEvent& event)
// {
//   wxLogDebug("MuniDisplay::OnClosePhotometry");
//   Unbind(wxEVT_CLOSE_WINDOW,&MuniDisplay::OnClosePhotometry,this,photometry->GetId());
//   //  photometry->Destroy();
//   photometry = 0;
// }



/*

   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


*/
