/*

  xmunipack - magnifier

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

  * There're unsolved mystery in sizer, layer algorithm which
    prevent setup of an initial size of this window to value
    of previously adjusted by user.

*/

#include "xmunipack.h"
#include "display.h"
#include "fits.h"
#include <wx/wx.h>
#include <wx/graphics.h>
#include <cstdlib>
#include <cmath>

long style = (wxDEFAULT_FRAME_STYLE | wxFRAME_TOOL_WINDOW) &
  ~(wxRESIZE_BORDER | wxMAXIMIZE_BOX | wxMINIMIZE_BOX);

MuniDisplayMagnifier::MuniDisplayMagnifier(wxWindow *w, MuniConfig *c):
  wxFrame(w,wxID_ANY,"Magnifier",wxDefaultPosition,wxDefaultSize,style),
  config(c)
{
  zoom = new MuniMagnifierGlass(this,config->magnifier_size,
				config->magnifier_scale);

  int n = int(log(double(config->magnifier_scale)) / 0.7 + 0.5);
  slider = new wxSlider(this,wxID_ANY,n,1,5,
			wxDefaultPosition,wxDefaultSize,
			wxSL_HORIZONTAL|wxSL_BOTTOM);

  wxSizerFlags slide_mark;
  slide_mark.Align(wxALIGN_CENTER_VERTICAL);

  wxBoxSizer *zoom_sizer = new wxBoxSizer(wxHORIZONTAL);
  zoom_sizer->Add(new wxStaticText(this,wxID_ANY,L"⊝"),slide_mark);
  zoom_sizer->Add(slider,wxSizerFlags(1));
  zoom_sizer->Add(new wxStaticText(this,wxID_ANY,L"⊕"),slide_mark);

  wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
  sizer->Add(zoom,wxSizerFlags(1).Expand());
  sizer->Add(zoom_sizer,wxSizerFlags().Expand().Border(wxLEFT|wxRIGHT));
  SetSizerAndFit(sizer);

  Bind(EVT_SLEW,&MuniDisplayMagnifier::OnMouseMotion,this);
  Bind(wxEVT_CLOSE_WINDOW,&MuniDisplayMagnifier::OnClose,this);
  Bind(wxEVT_SLIDER,&MuniDisplayMagnifier::OnScale,this);
}

void MuniDisplayMagnifier::OnClose(wxCloseEvent& event)
{
  //wxLogDebug("MuniDisplayMagnifier::OnClose");
  Hide();
  wxQueueEvent(GetParent(),event.Clone());
}

void MuniDisplayMagnifier::SetImage(const wxImage& i)
{
  zoom->SetImage(i);
}

void MuniDisplayMagnifier::UnsetImage()
{
  zoom->UnsetImage();
}

void MuniDisplayMagnifier::OnMouseMotion(MuniSlewEvent& event)
{
  wxQueueEvent(zoom,event.Clone());
}

void MuniDisplayMagnifier::OnScale(wxCommandEvent& event)
{
  wxQueueEvent(zoom,event.Clone());
}

int MuniDisplayMagnifier::GetScale() const
{
  return zoom->GetScale();
}


// -----

MuniMagnifierGlass::MuniMagnifierGlass(wxWindow *w, const wxSize& s, int c):
  wxWindow(w,wxID_ANY,wxDefaultPosition,s),
  width(s.GetWidth()), height(s.GetHeight()), scale(c), defined(false)
{
  int d = 4*42;
  SetMinSize(wxSize(d,d));

  Bind(wxEVT_PAINT,&MuniMagnifierGlass::OnPaint,this);
  Bind(wxEVT_SIZE,&MuniMagnifierGlass::OnSize,this);
  Bind(wxEVT_SLIDER,&MuniMagnifierGlass::OnScale,this);
  Bind(EVT_SLEW,&MuniMagnifierGlass::OnMouseMotion,this);
}

void MuniMagnifierGlass::SetImage(const wxImage& i)
{
  wxLogDebug("MuniMagnifierGlass::SetImage %d",i.IsOk());

  image = i;
  defined = image.IsOk();

  wxClientDC dc(this);
  if( defined )
    RandomBlur(dc);
  else
    Clear(dc);
}

void MuniMagnifierGlass::UnsetImage()
{
  wxLogDebug("MuniMagnifierGlass::UnsetImage");

  defined = false;
  image = wxImage();
  canvas = wxBitmap();
  wxClientDC dc(this);
  Clear(dc);
}

void MuniMagnifierGlass::OnPaint(wxPaintEvent& event)
{
  //  wxLogDebug("MuniMagnifierGlass::OnPaint");

  // re-draw scaled random image
  wxPaintDC dc(this);

  if( canvas.IsOk() )
    dc.DrawBitmap(canvas,0,0,false);
  else {
    Clear(dc);
  }
}

void MuniMagnifierGlass::OnSize(wxSizeEvent& event)
{
  //  wxLogDebug("MuniMagnifierGlass::OnSize");

  width = event.GetSize().GetWidth();
  height = event.GetSize().GetHeight();

  if( canvas.IsOk() ) {
    wxImage img = canvas.ConvertToImage();
    canvas = wxBitmap(img.Scale(width,height));
  }

  Refresh();
}

void MuniMagnifierGlass::UpdateCanvas(int i0, int j0, int width, int height)
{
  wxASSERT(scale > 0 && image.IsOk());

  int w = width / scale;
  int h = height / scale;

  int i1 = i0 - w/2;
  int j1 = image.GetHeight() - (j0 + h/2) - 1;
  wxRect rect(i1, j1, w+1, h+1);
  wxRect irect(0,0,image.GetWidth(),image.GetHeight());
  wxRect r = rect.Intersect(irect);
  int xoff = i1 < 0 ? w - r.width : 0;
  int yoff = j1 < 0 ? h - r.height : 0;

  if( ! rect.IsEmpty() ) {

    wxBitmap sub(image.GetSubImage(rect));
    wxBitmap bmp(width,height);

    wxMemoryDC mdc(sub);
    wxMemoryDC dc(bmp);

    dc.SetBackground(*wxGREY_BRUSH);
    dc.Clear();

    dc.StretchBlit(xoff*scale,yoff*scale,scale*r.width,scale*r.height,&mdc,
		   0,0,r.width,r.height);

    dc.SelectObject(wxNullBitmap);
    canvas = bmp;
  }
  else
    canvas = wxBitmap();

}

void MuniMagnifierGlass::OnScale(wxCommandEvent& event)
{
  scale = int(pow(2.0,double(event.GetInt())) + 0.5);
  //wxLogDebug("MuniMagnifierGlass::OnScale %d",scale);

  wxClientDC dc(this);

  if( defined )
    RandomBlur(dc);

  // draw grid
  wxASSERT(scale > 0);

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

    wxColour caper(138,184,230,196);
    gc->SetPen(wxPen(caper,1));

    int n = width / scale + 1;
    int m = height / scale + 1;

    for(int i = 0; i < n; i++)
      gc->StrokeLine(i*scale,0,i*scale,height);
    for(int j = 0; j < m; j++)
      gc->StrokeLine(0,j*scale,width,j*scale);

    // Show current zoom
    int x = width / 2;
    int y = height / 2;
    wxString a;
    a.Printf("%dx",scale);

    wxSize ts = GetTextExtent("32x");
    int dx = 2*ts.GetWidth();
    int dy = 2*ts.GetHeight();
    x =  width / 2 - dx / 2;
    y = height / 2 - dy / 2;

    gc->SetBrush(*wxWHITE_BRUSH);
    gc->SetPen(*wxGREY_PEN);
    gc->DrawRoundedRectangle(x,y,dx,dy,3);

    ts = GetTextExtent(a);
    x = width / 2 -  ts.GetWidth() / 2;
    y = height / 2 - ts.GetHeight()/ 2;

    wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
    gc->SetFont(font,*wxBLACK);
    gc->DrawText(a,x,y);

    gc->EndLayer();
    delete gc;
  }

}

int MuniMagnifierGlass::GetScale() const
{
  return scale;
}

void MuniMagnifierGlass::OnMouseMotion(MuniSlewEvent& event)
{
  if( ! defined ) return;

  if( event.leaving ) {
    wxClientDC dc(this);
    RandomBlur(dc);
    return;
  }

  if( ! event.inside ) {
    wxClientDC dc(this);
    UpdateBlur(dc);
    return;
  }

  // update zoom & draw crosshair
  crosshair = wxPoint(event.x,event.y);

  UpdateCanvas(crosshair.x,crosshair.y,width,height);

  wxClientDC dc(this);

  if( canvas.IsOk() )
    dc.DrawBitmap(canvas,0,0,false);

  // draw cross box
  int w =  width / scale;
  int h = height / scale;
  int ic = scale * (w / 2);
  int jc = scale * (h / 2);

  dc.SetBrush(*wxTRANSPARENT_BRUSH);
  dc.SetPen(*wxWHITE_PEN);
  dc.DrawRectangle(ic-2,jc-2,scale+4,scale+4);

  dc.SetPen(*wxBLACK_PEN);
  dc.DrawRectangle(ic-1,jc-1,scale+2,scale+2);

}

void MuniMagnifierGlass::UpdateBlur(wxDC& dc)
{
  if( defined && canvas.IsOk() ) {
    wxImage img = canvas.ConvertToImage();
    img = img.Blur(3);
    canvas = wxBitmap(img.Scale(width,height));
    dc.DrawBitmap(canvas,0,0,false);
    return;
  }

  Clear(dc);
}

void MuniMagnifierGlass::RandomBlur(wxDC& dc)
{
  const float rmax = RAND_MAX;

  if( defined ) {

    int w =  width / scale;
    int h = height / scale;
    int i = int( image.GetWidth() * (rand() / rmax));
    int j = int( image.GetHeight() *(rand() / rmax));

    wxRect rect(i, j, w, h);
    wxRect irect(0,0,image.GetWidth(),image.GetHeight());
    wxRect r = rect.Intersect(irect);
    if( ! r.IsEmpty() ) {
      wxImage img = image.GetSubImage(r);
      img = img.Blur(3);
      dc.DrawBitmap(wxBitmap(img.Scale(width,height)),0,0,false);
      return;
    }
  }

  Clear(dc);
}

void MuniMagnifierGlass::Clear(wxDC& dc)
{
  dc.SetBackground(*wxGREY_BRUSH);
  dc.Clear();
}


/*
program mag

  use fitsio

  implicit none

  real, dimension(400,400) :: obr
  integer :: status

  obr = 0
  obr(100,100) = 10
  obr(100,300) = 20
  obr(300,100) = 30
  obr(200,200) = 50

  status = 0
  call fits_create_file(15,'!/tmp/obr.fits',status)
  call fits_insert_img(15,16,2,[400,400],status)
  call fits_write_2d(15,obr,status)
  call fits_close_file(15,status)
  call fits_report_error('STDERR',status)

end program mag
*/
