/*

  xmunipack - display canvas

  Copyright © 2012-14, 17 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 <wx/wx.h>
#include <wx/scrolwin.h>
#include <wx/brush.h>
#include <wx/graphics.h>
#include <wx/renderer.h>
#include <wx/thread.h>
#include <list>

using namespace std;




// -- MuniDisplayCanvas

MuniDisplayCanvas::MuniDisplayCanvas(wxWindow *w, MuniConfig *c):
  wxScrolledCanvas(w,wxID_ANY),render(0),topwin(GetGrandParent()),config(c),handler(0),
  xoff(0), yoff(0),
  display(config->display_colorspace),
  zoom(config->display_zoom),zoomfit(-1.0),zooming(true),dragging(false),rendering(false),fit(false),astrometry(false)
{
  wxLogDebug("MuniDisplayCanvas constructor..");

  SetBackgroundStyle(wxBG_STYLE_PAINT);

  pal.SetPalette(config->display_pal);
  itt.SetItt(config->display_itt);
  display.SetTemperature(config->color_temperature);
  display.SetColorspace(config->display_colorspace);
  color.SetTrans("XYZ",config->cdatafile);
  color.SetWhitePoint(config->color_uwhitepoint,config->color_vwhitepoint);

  Bind(wxEVT_CLOSE_WINDOW,&MuniDisplayCanvas::OnClose,this);
  Bind(wxEVT_SIZE,&MuniDisplayCanvas::OnSize,this);
  Bind(wxEVT_SCROLLWIN_TOP,&MuniDisplayCanvas::OnScroll,this);
  Bind(wxEVT_SCROLLWIN_BOTTOM,&MuniDisplayCanvas::OnScroll,this);
  Bind(wxEVT_SCROLLWIN_LINEUP,&MuniDisplayCanvas::OnScroll,this);
  Bind(wxEVT_SCROLLWIN_PAGEUP,&MuniDisplayCanvas::OnScroll,this);
  Bind(wxEVT_SCROLLWIN_LINEDOWN,&MuniDisplayCanvas::OnScroll,this);
  Bind(wxEVT_SCROLLWIN_PAGEDOWN,&MuniDisplayCanvas::OnScroll,this);
  Bind(wxEVT_SCROLLWIN_THUMBRELEASE,&MuniDisplayCanvas::OnScroll,this);
  Bind(wxEVT_PAINT,&MuniDisplayCanvas::OnPaint,this);
  Bind(wxEVT_IDLE,&MuniDisplayCanvas::OnIdle,this);
  Bind(EVT_RENDER,&MuniDisplayCanvas::OnRenderFinish,this,ID_RENDER);
  Bind(EVT_RENDER,&MuniDisplayCanvas::OnSubRender,this,ID_SUBRENDER);
  Bind(EVT_CONFIG_UPDATED,&MuniDisplayCanvas::OnConfigUpdated,this);
  Bind(wxEVT_KEY_DOWN,&MuniDisplayCanvas::OnKeyDown,this);
  Bind(wxEVT_RIGHT_DOWN,&MuniDisplayCanvas::OnMenu,this);
}

MuniDisplayCanvas::~MuniDisplayCanvas()
{
  /*
  vector<MuniDrawBase *>::const_iterator i;
  for(i = drawobj.begin(); i != drawobj.end(); ++i)
    delete *i;
  */

  config->display_itt = itt.GetItt();
  config->display_pal = pal.GetPalette();
  config->display_zoom = zoom;

}

void MuniDisplayCanvas::OnClose(wxCloseEvent& event)
{
  //  wxLogDebug("MuniDisplayCanvas::OnClose");

  if( render ) {
    render->Delete();
    event.Veto();
    wxQueueEvent(this,new wxCloseEvent());
  }
  else
    event.Skip();
}

void MuniDisplayCanvas::OnScroll(wxScrollWinEvent& event)
{
  rendering = true;
  event.Skip();
}

void MuniDisplayCanvas::OnIdle(wxIdleEvent& event)
{
  // Rendering is started by Idle event FOLLOWING all (resize) events
  // The right canvas size is prepared for drawing.

  if( rendering ) {

    if( render ) {
      StopRendering();
      event.RequestMore();
    }
    else {
      rendering = false;
      Render();
    }
  }
}

void MuniDisplayCanvas::OnSize(wxSizeEvent& event)
{
  //  wxLogDebug("MuniDisplayCanvas::OnSize %d %d",
  //	     event.GetSize().GetWidth(),event.GetSize().GetHeight());

  if( canvas.IsOk() ) {
    wxSize s(GetSize());
    xoff = wxMax((s.GetWidth() - canvas.GetWidth())/2,0);
    yoff = wxMax((s.GetHeight() - canvas.GetHeight())/2,0);
    zoomfit = GetFitZoom();
  }

  Refresh();
  event.Skip();
}

void MuniDisplayCanvas::OnPaint(wxPaintEvent& event)
{
  //  wxLogDebug("OnPaint");
  wxPaintDC dc(this);

  /*
  dc.SetAxisOrientation(true,true);
  dc.SetDeviceOrigin(xoff,yoff);
  */

  dc.SetBackground(*wxBLACK_BRUSH);

  if( xoff > 0 || yoff > 0 ) // faster ?
    dc.Clear();

  int x0,y0,wc,hc;
  GetViewStart(&x0,&y0);
  GetClientSize(&wc,&hc);

  double z = 1.0;
  if( hdu.IsOk() && icon.IsOk() ) {
    z = (hdu.Width()*zoom)/icon.GetWidth();
  }

  wxRegionIterator upd = GetUpdateRegion();
  while(upd) {
    wxRect r = upd.GetRect();

    int xs = r.GetX();
    int ys = r.GetY();
    int ws = r.GetWidth();
    int hs = r.GetHeight();
    //    wxLogDebug("demaged: %d %d %d %d",xs,ys,ws,hs);

    int xw = xoff > 0 ? xs - xoff : x0 + xs;
    int yw = yoff > 0 ? ys - yoff : y0 + ys;
    int ww = ws - 2*xoff;
    int hw = hs - 2*yoff;

    if( icon.IsOk() ) {

      int xb = wxMax(xw/z,0);
      int yb = wxMax(yw/z,0);
      int wb = wxMin(ww/z + 1,icon.GetWidth());
      int hb = wxMin(hw/z + 1,icon.GetHeight());

      bool wt = 0 < wb && wb <= icon.GetWidth();
      bool ht = 0 < hb && hb <= icon.GetHeight();
      bool xt = xb >= 0 && xb + wb <= icon.GetWidth();
      bool yt = yb >= 0 && yb + hb <= icon.GetHeight();

      if( wt && ht && xt && yt) {
	wxImage i(icon.GetSubImage(wxRect(xb,yb,wb,hb)));
	i.Rescale(wb*z,hb*z);
	dc.DrawBitmap(wxBitmap(i),xs+xoff,ys+yoff);
      }
      else
	wxLogDebug("failed icon: %d %d %d %d",xb,yb,wb,hb);
    }

    if( canvas.IsOk() ) {

      int xb = wxMax(xw,0);
      int yb = wxMax(yw,0);
      int wb = wxMin(ww,canvas.GetWidth());
      int hb = wxMin(hw,canvas.GetHeight());

      bool wt = 0 < wb && wb <= canvas.GetWidth();
      bool ht = 0 < hb && hb <= canvas.GetHeight();
      bool xt = xb >= 0 && xb + wb <= canvas.GetWidth();
      bool yt = yb >= 0 && yb + hb <= canvas.GetHeight();

      //      wxLogDebug("canvas: %d %d %d %d %d %d %d %d %f %d %d %d %d %d %d %d",
      //		 xs,ys,ws,hs,xb,yb,wb,hb,zoom,canvas.GetWidth(),canvas.GetHeight(),x0,y0,xoff,yoff,
      //		 (int) wt && ht && xt && yt);

      if( wt && ht && xt && yt ) {

	wxImage i(canvas.GetSubImage(wxRect(xb,yb,wb,hb)));
	if( zoom > 1.0 ) i.Rescale(zoom*wb,zoom*hb);
	dc.DrawBitmap(wxBitmap(i),xs+xoff,ys+yoff);
      }
      else
	wxLogDebug("failed canvas: %d %d %d %d",xb,yb,wb,hb);

    }

    upd++;
  }

  // Layers
  list<MuniLayer>::const_iterator i;
  for(i = layers.begin(); i != layers.end(); ++i)
    DrawLayer(dc,*i);

}

void MuniDisplayCanvas::SetHandler(wxEvtHandler *h)
{
  handler = h;

  if( handler ) {
    Bind(wxEVT_MOTION,&MuniDisplayCanvas::OnMouseMotion,this);
    Bind(wxEVT_MOUSEWHEEL,&MuniDisplayCanvas::OnMouseWheel,this);
    Bind(wxEVT_LEFT_UP,&MuniDisplayCanvas::OnLeftUp,this);
  }
  else {
    Unbind(wxEVT_MOTION,&MuniDisplayCanvas::OnMouseMotion,this);
    Unbind(wxEVT_MOUSEWHEEL,&MuniDisplayCanvas::OnMouseWheel,this);
    Unbind(wxEVT_LEFT_UP,&MuniDisplayCanvas::OnLeftUp,this);
  }
}


void MuniDisplayCanvas::InitITT(const FitsArray& a, const FitsArrayStat& stat)
{
  if( a.IsColor() ) {
    itt.SetKind(ITT_KIND_ABS);
    itt.Init(FitsArrayStat(a.Plane(1)));
    color.Init(config->cdatafile,a);
  }
  else {
    itt.SetKind(ITT_KIND_REL);
    itt.Init(FitsArrayStat(a));
  }

  itt_orig = itt;
}

void MuniDisplayCanvas::SetHdu(const FitsArray& a, const wxImage& i, const FitsArrayStat& stat)
{
  hdu = a;
  icon = i;

  wxASSERT(hdu.IsOk() && icon.IsOk());

  Reset();

  InitITT(a,stat);

  fit = config->display_zoom <= 0.0;
  SetInitZoom(config->display_zoom ? -1.0 : 1.0);

  /*
  int w = wxMax(int(zoom*hdu.Width()),1);
  int h = wxMax(int(zoom*hdu.Height()),1);
  */

  InitDC(hdu.Width(),hdu.Height(),zoom);

  SetScrollRate(1,1);

  fitsimage = FitsImage(hdu);
  wxASSERT(fitsimage.IsOk());

  fimage = fitsimage;

  zoomfit = GetFitZoom();

  Refresh();
  rendering = true;

  /*
  MuniLayer l;
  l.objects.push_back(new MuniDrawCircle(382.5,255.0,382.5));
  layers.push_back(l);
  */
}

void MuniDisplayCanvas::InitDC(int w0, int h0, double z)
{
  //  wxLogDebug("MuniDisplayCanvas::InitDC %d %d %f",w0,h0,z);

  int wc,hc;
  GetClientSize(&wc,&hc);

  int w = z*w0;
  int h = z*h0;

  int wv = wxMax(w,wc);
  int hv = wxMax(h,hc);

  SetVirtualSize(wv,hv);

  if( z > 1.0 )
    canvas = wxImage(w0,h0);
  else
    canvas = wxImage(w,h);

  xoff = wxMax((wc - canvas.GetWidth())/2,0);
  yoff = wxMax((hc - canvas.GetHeight())/2,0);
  //  wxLogDebug("%d %d %d %d %f",w,h,xoff,yoff,z);

  canvas.InitAlpha();
  for(int i = 0; i < canvas.GetWidth(); i++)
    for(int j = 0; j < canvas.GetHeight(); j++)
      canvas.SetAlpha(i,j,wxIMAGE_ALPHA_TRANSPARENT);
}




void MuniDisplayCanvas::Reset()
{
  itt.SetItt(config->display_itt);
  itt = itt_orig;
  pal.SetPalette(config->display_pal);
  pal.SetNegative(false);
  color.SetSaturation(1.0);
  color.SetHue(0.0);
  color.SetNightVision(config->display_nvision);
  color.SetWhitePoint(config->color_uwhitepoint,config->color_vwhitepoint);
  color.SetNightThresh(0.0);
  color.SetNightWidth(30.0); // must correspods to initializer of Color?

  SetInitZoom(config->display_zoom ? -1.0 : 1.0);

  rendering = true;
}

void MuniDisplayCanvas::Render()
{
  //  wxASSERT(!wxIsBusy() && fitsimage.IsOk());
  //  wxLogDebug("MuniDisplayCanvas::Render");

  //stop when we are already rendering
  //StopRendering(); ?????

  // render image
  display.SetItt(itt);
  display.SetPalette(pal);
  display.SetColor(color);

  /*
  const vector<FitsArray> band = fitsimage.GetArrays();
  for(size_t i = 0; i < band.size(); i++) {
    if( band[i].IsOk() )
      wxLogDebug("%f %f %f",band[i].Pixel(1206,1276),band[i].Pixel(1211,1284),band[i].Pixel(911,614));
  }
  */

  int x0,y0,wc,hc,ws,hs;
  GetViewStart(&x0,&y0);
  GetClientSize(&wc,&hc);
  GetSize(&ws,&hs);

  double z = zoom > 1.0 ? 1.0 : zoom;
  int x = wxMax(int((x0 - xoff)/z),0);
  int y = wxMax(int((y0 - yoff)/z),0);
  int w = wxMin(int(wc/z),fitsimage.GetWidth());
  int h = wxMin(int(hc/z),fitsimage.GetHeight());

  if( ws > wc ) {
    x = x0;
    w = wc;
  }
  else {
    x = 0;
    w = wxMin(ws,fitsimage.GetWidth());
  }

  if( hs > hc ) {
    y = hc - y0;
    h = hc;
  }
  else {
    h = wxMin(hs,fitsimage.GetHeight());
    y = 0;
  }

  x = 0;
  y = 0;
  w = fitsimage.GetWidth();
  h = fitsimage.GetHeight();

  wxASSERT(zoom > 0.0);

  if( xoff > 0 )
    x = 0;
  else
    x = wxMax(x0,0)/zoom;

  if( yoff > 0 )
    y = 0;
  else
    y = wxMax(fitsimage.GetHeight() - (hs + y0)/zoom,0);

  w = wxMin(fitsimage.GetWidth(),ws/zoom);
  h = wxMin(fitsimage.GetHeight(),hs/zoom);

  //  wxLogDebug("%f %d %d %d %d %d %d %d %d %d %d",zoom,x,y,w,h,x0,y0,wc,hc,xoff,yoff);

  wxASSERT(render == 0);
  render = new ImageRender(this,fitsimage,fimage,display,zoom,zooming,x,y,w,h);
  wxASSERT(render);
  wxThreadError code = render->Create();
  wxASSERT(code == wxTHREAD_NO_ERROR);
  render->Run();

  //  ::wxBeginBusyCursor();
  Refresh();
}


void MuniDisplayCanvas::OnSubRender(MuniRenderEvent& event)
{
  //  wxLogDebug("MuniDisplay::OnSubRender %d %d %d %d %d",event.x,event.y,event.width,event.height,(int)renderid);

  picture = event.picture;
  wxASSERT(picture.IsOk());

  wxImage img(picture.GetWidth(),picture.GetHeight(),picture.NewTopsyTurvyRGB());

  int x0,y0;
  GetViewStart(&x0,&y0);

  wxASSERT(canvas.IsOk() && canvas.HasAlpha());
  for(int i = 0; i < event.width; i++)
    for(int j = 0; j < event.height; j++) {
      int k = event.x + x0 + i;
      int l = event.y + y0 + j;
      if( 0 <= k && k < canvas.GetWidth() && 0 <= l && l < canvas.GetHeight() ) {
	canvas.SetRGB(wxRect(k,l,1,1),img.GetRed(i,j),img.GetGreen(i,j),img.GetBlue(i,j));
	canvas.SetAlpha(k,l,wxIMAGE_ALPHA_OPAQUE);
      }
    }
  Refresh();
}

void MuniDisplayCanvas::OnRenderFinish(MuniRenderEvent& event)
{
  //  wxLogDebug("MuniDisplay::OnRenderFinish");

  fimage = event.image;
  zooming = false;

  //  wxASSERT(render == 0);
  //  render = 0;

  // if ! fimage.IsOk() ?????
  //  OverlayBitmap();

  Refresh();

  //  wxASSERT(::wxIsBusy()); // ??? failed randomly?
  //  ::wxEndBusyCursor();
}

void MuniDisplayCanvas::StopRendering()
{
  //  wxLogDebug("MuniDisplayCanvas::StopRendering()");
  {
    wxCriticalSectionLocker enter(renderCS);
    if( render )
      render->Delete();
  }

  while(true) {
    {
      wxCriticalSectionLocker enter(renderCS);
      if( ! render ) break;
    }
    ::wxMilliSleep(1);
  }
}

void MuniDisplayCanvas::ConvertCoo(double u, double v, double& x, double& y)
{
  double height = canvas.GetHeight();
  if( zoom > 1.0 ) height = zoom*height;

  x = xoff + zoom*u;
  y = yoff + height - zoom*v;
  x = x - vbX;
  y = y - vbY;

  /*
  x = u;
  y = height - v;
  */
}

void MuniDisplayCanvas::DrawLayer(wxPaintDC& dc, const MuniLayer& layer)
{
  //  wxLogDebug("DrawLayer");
  //  wxStopWatch sw;
  //DoPrepareDC(dc);

  double height = canvas.GetHeight();

  // Find Out where the window is scrolled to
  //  vbX,vbY;                     // Top left corner of client
  GetViewStart(&vbX,&vbY);

  //  wxLogDebug("%d",(int)layer.objects.size());

  //  wxColour blue(wxBLUE->Red(),wxBLUE->Green(),wxBLUE->Blue(),50);

  /*
  dc.SetBrush(wxBrush(blue));
  dc.SetPen(wxPen(*wxYELLOW,1));

  for(size_t i = 0; i < layer.objects.size(); i++) {

    vector<wxObject *>::const_iterator i;
    for(i = layer.objects.begin(); i != layer.objects.end(); ++i) {

      MuniDrawCircle *circle = dynamic_cast<MuniDrawCircle *>(*i);
      if( circle ) {
	float d = circle->r*zoom;
	float x = xoff + zoom*circle->x;
	float y = yoff + (height - zoom*circle->y);
	dc.DrawCircle(x,y,d);
      }

      MuniDrawLine *line = dynamic_cast<MuniDrawLine *>(*i);
      if( line ) {
	wxPoint2DDouble points[2];
	points[0] = wxPoint2DDouble(zoom*line->x1,height-zoom*line->y1);
	points[1] = wxPoint2DDouble(zoom*line->x2,height-zoom*line->y2);
	dc.DrawLine(zoom*line->x1,height-zoom*line->y1,
		    zoom*line->x2,height-zoom*line->y2);
      }

      MuniDrawText *text = dynamic_cast<MuniDrawText *>(*i);
      if( text ) {
	dc.DrawText(text->text,zoom*text->x,height-zoom*text->y);
      }
    }

  }
  */

  //  wxLogDebug("%f %f %f %f %f %f ",(float)xoff,(float)yoff,(float)zoom,(float)vbX,(float)vbY,height);

  wxGraphicsContext *gc = wxGraphicsContext::Create(dc);
  if( gc ) {
    gc->SetAntialiasMode(wxANTIALIAS_NONE);
    gc->BeginLayer(1.0);

    // default parameters
    gc->SetFont(*wxNORMAL_FONT,*wxWHITE);
    gc->SetPen(wxPen(*wxWHITE,1));
    gc->SetBrush(*wxBLUE_BRUSH);

    // switch-off in fast-update mode?
    gc->SetAntialiasMode(wxANTIALIAS_DEFAULT);

    //    gc->SetBrush(wxBrush(wxColour(90,90,255,130)));
    //    gc->SetBrush(*wxBLUE_BRUSH);
    //    gc->SetPen(wxPen(*wxWHITE,1));
    //    gc->DrawEllipse(100.0,100.0,100.0,100.0);


    vector<wxObject *>::const_iterator i;
    vector<wxObject *> objects = layer.GetObjects();
    for(i = objects.begin(); i != objects.end(); ++i) {

      MuniDrawPen *pen = dynamic_cast<MuniDrawPen *>(*i);
      if( pen )
	gc->SetPen(pen->pen);
	//	gc->SetPen(wxPen(pen->colour,pen->width));

      MuniDrawBrush *brush = dynamic_cast<MuniDrawBrush *>(*i);
      if( brush )
	gc->SetBrush(brush->brush);

      MuniDrawCircle *circle = dynamic_cast<MuniDrawCircle *>(*i);
      if( circle ) {
	double r = wxMax(circle->r * zoom,2);
	double d = 2*r;

	double x,y;
	ConvertCoo(circle->x,circle->y,x,y);
	//wxLogDebug("%f %f %f %f",x,y,circle->r,d);
	x = x - r;
	y = y - r;
	gc->DrawEllipse(x,y,d,d);
      }

      MuniDrawLine *line = dynamic_cast<MuniDrawLine *>(*i);
      if( line ) {
	wxPoint2DDouble points[2];
	double x,y;
	ConvertCoo(line->x1,line->y1,x,y);
	points[0] = wxPoint2DDouble(x,y);
	ConvertCoo(line->x2,line->y2,x,y);
	points[1] = wxPoint2DDouble(x,y);
	//	wxLogDebug("%f %f %f %f %f %f",line->x1,line->y1,line->x2,line->y2,x,y);
	/*
	points[0] = wxPoint2DDouble(xoff+zoom*line->x1,yoff+height-zoom*line->y1);
	points[1] = wxPoint2DDouble(xoff+zoom*line->x2,yoff+height-zoom*line->y2);
	*/
	gc->DrawLines(2,points);
      }

      MuniDrawCross *cross = dynamic_cast<MuniDrawCross *>(*i);
      if( cross ) {
	wxPoint2DDouble points[2];
	double x,y;
	ConvertCoo(cross->x,cross->y,x,y);
	points[0] = wxPoint2DDouble(x-cross->r,y);
	points[1] = wxPoint2DDouble(x+cross->r,y);
	gc->DrawLines(2,points);
	points[0] = wxPoint2DDouble(x,y-cross->r);
	points[1] = wxPoint2DDouble(x,y+cross->r);
	gc->DrawLines(2,points);
	/*
	points[0] = wxPoint2DDouble(xoff+zoom*(cross->x-cross->r),yoff+height-zoom*cross->y);
	points[1] = wxPoint2DDouble(xoff+zoom*(cross->x+cross->r),yoff+height-zoom*cross->y);
	gc->DrawLines(2,points);
	points[0] = wxPoint2DDouble(xoff+zoom*cross->x,yoff+height-zoom*(cross->y-cross->r));
	points[1] = wxPoint2DDouble(xoff+zoom*cross->x,yoff+height-zoom*(cross->y+cross->r));
	gc->DrawLines(2,points);
	gc->DrawLines(2,points);
	*/
      }

      MuniDrawFont *font = dynamic_cast<MuniDrawFont *>(*i);
      if( font )
	gc->SetFont(font->font,font->colour);

      MuniDrawText *text = dynamic_cast<MuniDrawText *>(*i);
      if( text ) {
	double w,h,d,l;
	double x = text->x > 0.0 ? text->x : text->x;
	double y = text->y > 0.0 ? text->y : text->y;
	gc->GetTextExtent(text->text,&w,&h,&d,&l);
	if( text->angle < 0.01 )
	  gc->DrawText(text->text,xoff+zoom*(x-w/2.0),yoff+height-zoom*y);
	else if( fabs(text->angle - 1.5708) < 0.01 )
	  gc->DrawText(text->text,xoff+zoom*(x-h),yoff+height-zoom*(y - w/2.0),text->angle);
      }

      MuniDrawRectangle *rect = dynamic_cast<MuniDrawRectangle *>(*i);
      if( rect ) {
	double x,y;
	ConvertCoo(rect->x,rect->y-rect->h,x,y);
	gc->DrawRectangle(x,y,rect->w,rect->h);
	//	gc->DrawRectangle(xoff+zoom*rect->x,yoff+zoom*(height-rect->y-rect->h),rect->w,rect->h);
      }

      MuniDrawBitmap *bitmap = dynamic_cast<MuniDrawBitmap *>(*i);
      if( bitmap ) {
	double x,y;
	ConvertCoo(bitmap->x,bitmap->y-bitmap->h,x,y);
	gc->DrawBitmap(bitmap->bitmap,x,y,bitmap->w,bitmap->h);

	//	gc->DrawBitmap(bitmap->bitmap,xoff+bitmap->x,yoff+(height-bitmap->y-bitmap->h),bitmap->w,bitmap->h);
      }

    }

    gc->EndLayer();
    delete gc;
  }


  //  wxLogDebug("Drawing took: %f sec",sw.Time()/1000.0);
}

// void MuniDisplayCanvas::DrawSVG(wxPaintDC& dc, const wxXmlDocument& svg)
// {
//   if( !(svg.IsOk() && svg.GetRoot()->GetName() == "svg") ) return;

//   wxGraphicsContext *gc = wxGraphicsContext::Create(dc);
//   if( gc ) {

//     // default colors
//     gc->SetBrush(wxBrush(*wxBLUE));
//     gc->SetPen(wxPen(*wxYELLOW,1));
//     gc->SetFont(*wxNORMAL_FONT,*wxWHITE);

//     wxXmlNode *child = svg.GetRoot()->GetChildren();
//     while (child) {

//       if (child->GetName() == "line") {

// 	double x1,y1,x2,y2;

// 	wxXmlAttribute *prop = child->GetAttributes();
// 	while(prop) {

// 	  wxString a = prop->GetValue();
// 	  double x;
// 	  a.ToDouble(&x);

// 	  if( prop->GetName() == "x1" )
// 	    x1 = x;
// 	  else if( prop->GetName() == "y1" )
// 	    y1 = x;
// 	  else if( prop->GetName() == "x2" )
// 	    x2 = x;
// 	  else if( prop->GetName() == "y2" )
// 	    y2 = x;

// 	  prop = prop->GetNext();
// 	}
// 	gc->StrokeLine(x1,y1,x2,y2);

//       }

//       else if( child->GetName() == "text") {

// 	double x,y;
// 	wxString text(child->GetNodeContent());

// 	wxXmlAttribute *prop = child->GetAttributes();
// 	while(prop) {

// 	  wxString a = prop->GetValue();
// 	  double t;
// 	  a.ToDouble(&t);

// 	  if( prop->GetName() == "x" )
// 	    x = t;
// 	  else if( prop->GetName() == "y" )
// 	    y = t;

// 	  prop = prop->GetNext();
// 	}

// 	gc->DrawText(text,x,y);
//       }

//       else if( child->GetName() == "circle") {

// 	double cx,cy,r;

// 	wxXmlAttribute *prop = child->GetAttributes();
// 	while(prop) {

// 	  wxString a = prop->GetValue();
// 	  double x;
// 	  a.ToDouble(&x);

// 	  if( prop->GetName() == "cx" )
// 	    cx = x;
// 	  else if( prop->GetName() == "cy" )
// 	    cy = x;
// 	  else if( prop->GetName() == "r" )
// 	    r = x;

// 	  prop = prop->GetNext();
// 	}

// 	gc->DrawEllipse(cx-r/2.0,cy-r/2.0,r,r);
//       }
//       child = child->GetNext();
//     }
//     delete gc;
//   }

// }

void MuniDisplayCanvas::OnMouseMotion(wxMouseEvent& event)
{
  if( astrometry )
    return;

  wxClientDC dc(this);
  DoPrepareDC(dc);

  if( event.Moving() ) {

    //    wxLogDebug(" %d %d",event.GetLogicalPosition(dc).x,event.GetLogicalPosition(dc).y);

    wxPoint cursor = event.GetLogicalPosition(dc) - wxPoint(xoff,yoff);
    MuniSlewEvent evt(xEVT_SLEW);
    evt.z_x = cursor.x;
    evt.z_y = canvas.GetHeight() - cursor.y;
    evt.i_x = cursor.x/zoom;
    evt.i_y = (canvas.GetHeight() - cursor.y)/zoom;

    //    wxLogDebug("%d %d %d %d",evt.i_x,evt.i_y,evt.z_x,evt.z_y);

    int size = config->detail_zoom;

    int x = evt.i_x;
    int y = evt.i_y;

    int x1 = x - size/2;
    int y1 = y - size/2;
    int x2 = x + size/2;
    int y2 = y + size/2;

    if( fitsimage.IsOk() &&
	x1 > 0 && y1 > 0 && x2 < fitsimage.GetWidth() && y2 < fitsimage.GetHeight() ){
      evt.picture = display.GetImage(fitsimage.GetSubImage(x1,y1,size,size));
    }

    wxASSERT(handler);
    wxQueueEvent(handler,evt.Clone());
  }
  else if( event.Dragging() ) {


    if( ! dragging ) {

      SetCursor(wxCursor(wxCURSOR_HAND));
      xdrag0 = event.GetLogicalPosition(dc).x;
      ydrag0 = event.GetLogicalPosition(dc).y;
      dragging = true;

    }
    else if( dragging ) {

      int x0,y0,dx,dy;
      GetViewStart(&x0,&y0);
      GetScrollPixelsPerUnit(&dx,&dy);
      wxASSERT(dx > 0 && dy > 0);
      int i = x0 - (event.GetLogicalPosition(dc).x - xdrag0) / dx;
      int j = y0 - (event.GetLogicalPosition(dc).y - ydrag0) / dy;
      Scroll(i,j);

    }

  }

}

void MuniDisplayCanvas::OnMouseWheel(wxMouseEvent& event)
{
  int id = event.GetWheelRotation() > 0 ? wxID_ZOOM_IN : wxID_ZOOM_OUT ;
  wxQueueEvent(topwin,new wxCommandEvent(wxEVT_COMMAND_MENU_SELECTED,id));
}

void MuniDisplayCanvas::OnLeftUp(wxMouseEvent& event)
{
  if( dragging ) {
    SetCursor(*wxSTANDARD_CURSOR);
    dragging = false;
    rendering = true;
  }
}

void MuniDisplayCanvas::OnStick(wxCommandEvent& event)
{
  stick = event.IsChecked();
}

void MuniDisplayCanvas::OnMenu(wxMouseEvent& WXUNUSED(event))
{
  wxTopLevelWindow *twin = static_cast<wxTopLevelWindow *>(topwin);
  if( !twin ) return;

  wxMenu popup;

  if( twin->IsFullScreen() ) {
    popup.Append(ID_MENU_FULLSCREEN,"Leave Fullscreen");
    popup.AppendSeparator();
    popup.Append(wxID_ZOOM_100,"Normal size\tCtrl+0");
    popup.Append(wxID_ZOOM_IN,"Zoom\tCtrl++");
    popup.Append(wxID_ZOOM_OUT,"Shrink\tCtrl+-");
    popup.Append(wxID_ZOOM_FIT,"Best fit\tCtrl+*");
    popup.Append(ID_CYCLE_ITT,"Cycle Itt\tCtrl+T");
    popup.Append(ID_CYCLE_PAL,"Cycle Palette\tCtrl+P");
  }
  else /*if( ! twin->IsFullScreen() )*/ {
    popup.AppendCheckItem(ID_STICK,"Stick"); // Ctrl+S does not works
    popup.Check(ID_STICK,stick);
  }

  PopupMenu(&popup);
}


void MuniDisplayCanvas::OnKeyDown(wxKeyEvent& event)
{
  long keycode = event.GetKeyCode();

  wxTopLevelWindow *twin = static_cast<wxTopLevelWindow *>(topwin);

  if( twin && twin->IsFullScreen() && keycode == WXK_ESCAPE ){
    wxQueueEvent(topwin,new wxCommandEvent(wxEVT_COMMAND_MENU_SELECTED,ID_FULLSCREEN));
    return;
  }

  if( keycode == WXK_ESCAPE ) {
    StopRendering();
    return;
  }

  if( event.GetModifiers() & wxMOD_CMD ) {
    int id = wxID_ANY;
    if( keycode == WXK_NUMPAD_MULTIPLY )
      id = wxID_ZOOM_FIT;
    else if( ! fit ) {
      switch(keycode) {
      case WXK_NUMPAD_ADD:      id = wxID_ZOOM_IN;  break;
      case WXK_NUMPAD_SUBTRACT: id = wxID_ZOOM_OUT; break;
      case WXK_NUMPAD0:         id = wxID_ZOOM_100; break;
      case WXK_NUMPAD_MULTIPLY: id = wxID_ZOOM_FIT; break;
      case WXK_LEFT:            id = wxID_BACKWARD; break;
      case WXK_RIGHT:           id = wxID_FORWARD;  break;
      }
    }

    if( id != wxID_ANY ) {
      wxQueueEvent(topwin,new wxCommandEvent(wxEVT_COMMAND_MENU_SELECTED,id));
      return;
    }
  }

  long x = event.GetX();
  long y = event.GetY();

  switch(keycode) {
  case WXK_LEFT:
    x--; break;
  case WXK_UP:
    y--; break;
  case WXK_RIGHT:
    x++; break;
  case WXK_DOWN:
    y++; break;
  }
  WarpPointer(x,y);

  event.Skip();
}

void MuniDisplayCanvas::OnTuneFine(MuniTuneEvent& e)
{
  //  wxLogDebug("MuniDisplayCanvas::OnTuneFine");

  double oldzoom = zoom;

  switch(e.GetId()) {
  case ID_ITT_TYPE:   itt.SetItt(e.GetString()); break;
  case ID_ITT_OFFSET: itt.SetBlack(e.x); break;
    //  case ID_ITT_SLOPE:  itt.SetSensitivity(e.x); break;
  case ID_ITT_BRIGHTNESS: itt.SetBrightness(e.x); break;
  case ID_ITT_CONTRAST:  itt.SetContrast(e.x); break;
  case ID_ITT_AMP:    itt.SetAmp(e.x); break;
  case ID_ITT_ZERO:   itt.SetZero(e.x); break;

  case ID_COLOR_NVISION: color.SetNightVision(e.GetInt()); break;
  case ID_COLOR_SATUR:   color.SetSaturation(e.x); break;
  case ID_COLOR_HUE:     color.SetHue(e.x); break;
  case ID_COLOR_WHITEU:  color.SetWhitePoint(e.x,-1.0); break;
  case ID_COLOR_WHITEV:  color.SetWhitePoint(-1.0,e.x); break;
  case ID_COLOR_NTHRESH: color.SetNightThresh(e.x); break;
  case ID_COLOR_NWIDTH:  color.SetNightWidth(e.x); break;
  case ID_COLOR_MEAN:    color.SetLevel(e.index,e.x); break;
  case ID_COLOR_WEIGHT:  color.SetWeight(e.index,e.x); break;

  case ID_PALETTE_TYPE:     pal.SetPalette(e.GetString()); break;
  case ID_PALETTE_NEGATIVE: pal.SetNegative(e.GetInt()); break;

  case ID_ZOOM_SCALE: zoom = e.x > 0.0 ? e.x : zoomfit; fit = e.fit; break;

  case ID_RESET: Reset(); break;

  default: wxLogDebug("WARNING: MuniDisplay::OnTuneFine - unknown ID");
  }

  if( e.GetId() == ID_ZOOM_SCALE ) {

    int x0,y0;
    GetViewStart(&x0,&y0);

    int wc,hc;
    GetClientSize(&wc,&hc);

    int ux,uy;
    GetScrollPixelsPerUnit(&ux,&uy);

    // save old position of center
    double xcold = (x0 + wc/2 - xoff)/(hdu.Width()*oldzoom);
    double ycold = (y0 + hc/2 - yoff)/(hdu.Height()*oldzoom);

    int w = wxMax(int(zoom*hdu.Width()),1);
    int h = wxMax(int(zoom*hdu.Height()),1);

    int x = wxMax(int(hdu.Width()*zoom*xcold+xoff-wc/2),0);
    int y = wxMax(int(hdu.Height()*zoom*ycold+yoff-hc/2),0);

    if( x + wc > w ) x = wxMax(w - wc - 1,0);
    if( y + hc > h ) y = wxMax(h - hc - 1,0);

    // wxLogDebug("+ %f %f %d %d %d %d %d %d %d %d %d %d %f",xcold,ycold,wc,hc,ux,uy,x/ux,y/uy,x,y,w,h,zoom);

    Scroll(x/ux,y/uy);

    // InitDC must follows (!) Scroll. Why?
    InitDC(hdu.Width(),hdu.Height(),zoom);

    if( zoom > 1.0 ) {
      Refresh();
      return;
    }
  }


  int x0,y0,wc,hc;
  GetViewStart(&x0,&y0);
  GetClientSize(&wc,&hc);
  if( zoom <= zoomfit  ) {
    x0 = 0;
    y0 = 0;
    wc = zoom*hdu.Width();
    hc = zoom*hdu.Height();
  }

  Refresh();
  rendering = true;
}

void MuniDisplayCanvas::OnAstrometry(MuniAstrometryEvent& e)
{
  //  wxLogDebug("MuniDisplayCanvas::OnAstrometry");

  RemoveLayers(ID_ASTROMETRY);

  /*
  list<MuniLayer>::iterator i;
  for( i = layers.begin(); i != layers.end(); ++i)
    if( i->GetId() == ID_ASTROMETRY ) {
      layers.erase(i);
      break;
    }
  */

  astrometry = e.astrometry;
  layers.clear();

  if( astrometry ) {

    //wxLogDebug(e.proj+" %f %f %f",e.xcen,e.ycen,e.scale);

    aangle = e.angle;
    ascale = e.scale;
    acen = e.acen;
    dcen = e.dcen;
    xcen = e.xcen;
    ycen = e.ycen;
    //  layers.push_back(e.svg);
    layers.push_back(e.layer);
//    wxLogDebug("astrometryevent: %d",(size_t)e.layer.objects.size());
//    wxLogDebug("astrometryevent: %d",(size_t)e.layer.IsEmpty());
  }

  Refresh();
}

void MuniDisplayCanvas::OnPhotometry(MuniPhotometryEvent& e)
{
  wxLogDebug("MuniDisplayCanvas::OnPhotometry %d",(int)e.erase);

  if( e.erase )
    RemoveLayers(ID_PHOTOMETRY);
  else
    layers.push_back(e.layer);

  Refresh();
}

void MuniDisplayCanvas::AddLayer(const MuniLayer& ml)
{
  layers.push_back(ml);
  Refresh();
}

void MuniDisplayCanvas::RemoveLayers(int id)
{
  list<MuniLayer>::iterator i = layers.begin();
  while( i != layers.end() ) {
    if( i->GetId() == id )
      i = layers.erase(i);
    else
      ++i;
  }

  Refresh();
}

void MuniDisplayCanvas::OnConfigUpdated(wxCommandEvent& event)
{
  display.SetTemperature(config->color_temperature);
  display.SetColorspace(config->display_colorspace);
  color.SetWhitePoint(config->color_uwhitepoint,config->color_vwhitepoint);
  rendering = true;
}

double MuniDisplayCanvas::GetFitZoom() const
{
  if( ! hdu.IsOk() ) return 1.0;

  wxSize isize(hdu.Width(),hdu.Height());
  wxSize csize = GetClientSize();

  if( isize.GetWidth() > 0 && isize.GetHeight() > 0 ) {
    double sx = double(csize.GetWidth())/double(isize.GetWidth());
    double sy = double(csize.GetHeight())/double(isize.GetHeight());
    return  sx > sy ? sy : sx;
  }
  else if( isize.GetWidth() > 0 && isize.GetHeight() == 0 ) {
    double sx = double(csize.GetWidth())/double(isize.GetWidth());
    return  sx;
  }

  return 1.0;
}


void MuniDisplayCanvas::SetInitZoom(double z)
{
  if( z > 0.0 )
    zoom = z;
  else {
    double zz = GetFitZoom();
    zoom = zz < 1.0 ? zz : 1.0;
  }
  zooming = true;
}

/*
void MuniDisplayCanvas::OverlayBitmap(const vector<MuniDrawBase *>& drawobj)
{
  wxASSERT(bitmap.IsOk());

  wxBitmap layer = wxBitmap(bitmap.GetWidth(),bitmap.GetHeight());

  wxMemoryDC mdc(layer);
  mdc.SetBackground(*wxBLACK_BRUSH);
  mdc.Clear();
  int height = layer.GetHeight();

  wxGraphicsContext *gc = wxGraphicsContext::Create(mdc);
  if( gc ) {

    gc->SetBrush(wxBrush(*wxBLUE));
    gc->SetPen(wxPen(*wxYELLOW,1));

    vector<MuniDrawBase *>::const_iterator i;
    for(i = drawobj.begin(); i != drawobj.end(); ++i) {

      MuniDrawCircle *e = static_cast<MuniDrawCircle *>(*i);
      if( e ) {
	float r = e->r;
	float x = e->cx - r/2.0;
	float y = height - (e->cy + r/2.0);
	gc->DrawEllipse(x,y,r,r);
      }

    }
    delete gc;
  }
  mdc.SelectObject(wxNullBitmap);

  wxImage i = layer.ConvertToImage();
  i.SetMaskColour(0,0,0);

  overlay.push_back(wxBitmap(i));

}
*/

 //void MuniDisplayCanvas::OverlayGrid(const vector<wxGraphicsPath>& paths)
 //{
  // wxASSERT(bitmap.IsOk());

  // wxBitmap layer = wxBitmap(bitmap.GetWidth(),bitmap.GetHeight());

  // wxMemoryDC mdc(layer);
  // mdc.SetBackground(*wxBLACK_BRUSH);
  // mdc.Clear();
  // int height = layer.GetHeight();

  // wxGraphicsContext *gc = wxGraphicsContext::Create(mdc);
  // if( gc ) {

  //   gc->SetBrush(wxBrush(*wxBLUE));
  //   gc->SetPen(wxPen(*wxYELLOW,1,wxPENSTYLE_SOLID));

  //   wxGraphicsPath path = gc->CreatePath();

  //   path.MoveToPoint(333,333);
  //   path.AddLineToPoint(500,500);

  //   /*
  //   vector<wxGraphicsPath>::const_iterator i;
  //   for(i = paths.begin(); i != paths.end(); ++i) {
  //   */

  //   gc->StrokePath(path);

  // }
  // delete gc;
  // mdc.SelectObject(wxNullBitmap);

  // wxImage i = layer.ConvertToImage();
  // i.SetMaskColour(0,0,0);

  // overlay.push_back(wxBitmap(i));

 //}



// ---- ImageRender ---------------------------------

ImageRender::ImageRender(wxEvtHandler *eh, const FitsImage& fi,
			 const FitsImage& fis, const FitsDisplay& d,
			 double zz, bool z, int x, int y, int w, int h):
  wxThread(wxTHREAD_DETACHED),handler(eh),fitsimage(fi), fimage(fis),
  display(d), zoom(zz), zooming(z),x0(x),y0(y),width(w),height(h)
{
  wxASSERT(handler && fitsimage.IsOk() && x >= 0 && y >= 0 && h > 0 && w > 0);
  //  wxLogDebug("ImageRender::ImageRender %f %d %d %d",zoom,(int)zooming,
  //	     (int)fimage.IsOk() ? fimage.GetWidth():0,
  //	     (int)fimage.IsOk() ? fimage.GetHeight():0);
}

ImageRender::~ImageRender()
{

  wxCriticalSectionLocker enter(static_cast<MuniDisplayCanvas *>(handler)->renderCS);
  static_cast<MuniDisplayCanvas *>(handler)->render = 0;

  //  wxLogDebug("ImageRender::~ImageRender");
}

wxThread::ExitCode ImageRender::Entry()
{
  bool test_destroy = false;

#ifdef __WXDEBUG__
  wxStopWatch sw;
#endif

  // length of square side
  // This value is choosed to be the duration of a single iteration
  // shorter than an user reaction time (~0.1 sec). Note that
  // short sides implicates frequent generation of events and memory
  // allocations. So the value must by determined carefully.
  const int side = 3*137;

  FitsImage newimage;

  if( false /*fimage.IsOk() && ! zooming*/ )
    newimage = fimage;

  else {
    zooming = true;
    fimage = fitsimage;
    newimage = fitsimage;//.GetSubImage(x0,y0,width,height);
    //    newimage.Rescale(zoom);
  }


  //  wxLogDebug("Init took: %d msec",(int)sw.Time());

  int w = fimage.GetWidth();
  int h = fimage.GetHeight();
  w = width;
  h = height;
  //  newimage = FitsImage(w,h);

  //  int nw = width;
  int htotal = height*zoom;


  int dx = 0, dy = 0;

  int x = 0;
  int y = 0;

  int imax = wxMin(x0 + w,fitsimage.GetWidth());
  int jmax = wxMin(y0 + h,fitsimage.GetHeight());

  //  for(int j = 0; j < h ; j += side) {
  for(int j = y0; j < jmax; j += side) {

    x = 0;

    //    for(int i = 0; i < w; i += side) {
    for(int i = x0; i < imax; i += side) {

      //      wxLogDebug("Piece rendering took: %d msec",(int)sw.Time());

      if( TestDestroy() ) {
	test_destroy = true;
	//	wxLogDebug("Rendering took when delete: %f sec",sw.Time()/1000.0);
	goto breake;
      }

      //      wxASSERT(w > i && h > j);

      //      wxASSERT(x0 <= i && i <
      dx = wxMin(imax - i,side);
      dy = wxMin(jmax - j,side);

      //      wxLogDebug("%d %d %d %d",dx,dy,i,j);
      wxASSERT(dx > 0 && dy > 0 && fimage.IsOk());

      FitsImage sub(fimage.GetSubImage(i,j,dx,dy));

      if( zooming ) {
	FitsImage z(sub.Scale(zoom));
	sub = z;
	//	wxASSERT(dx == sub.GetWidth() && dy == sub.GetHeight());
	dx = wxMin(dx,sub.GetWidth());
	dy = wxMin(dy,sub.GetHeight());
      }

      //      wxLogDebug("sub: %d %d %d %d",x,y,newimage.GetWidth(),newimage.GetHeight());
      wxASSERT(sub.IsOk());
      //      newimage.SetSubImage(x,y,sub);

      wxASSERT(sub.IsOk());

      MuniRenderEvent ev(EVT_RENDER,ID_SUBRENDER);
      ev.picture = display.GetImage(sub);
      ev.x = x;
      ev.y = htotal - y - dy;
      ev.x0 = x0;
      ev.y0 = y0;
      ev.width = dx;
      ev.height = dy;
      //            ev.y = newimage.GetHeight() - y - dy;
      wxQueueEvent(handler,ev.Clone());

      //      wxASSERT(dx > 0);
      x = x + dx;
    }
    //    wxASSERT(dy > 0);
    y = y + dy;
  }

 breake:

  MuniRenderEvent ev(EVT_RENDER,ID_RENDER);
  if( test_destroy )
    ev.image = FitsImage();
  else {
    ev.image = FitsImage(newimage);
    ev.x0 = x0;
    ev.y0 = y0;
    ev.width = width;
    ev.height = height;
  }
  wxQueueEvent(handler,ev.Clone());

  //  wxLogDebug("Rendering took: %f sec",sw.Time()/1000.0);
  return (wxThread::ExitCode) 0;
}
