/*

  xmunipack - icon


  Copyright © 2009-2012, 2018-9 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/>.



  IMPORTANT

  ***  All functions MUST BE called in main thread ! ***

  .. else the execution will be locked by wxImage::ConvertToImage();


*/


#include "icon.h"
#include <cstring>
#include <wx/wx.h>
#include <wx/graphics.h>

using namespace std;


MuniIcon::MuniIcon(const FitsFile& f, const wxString& cs, const wxString& cd,
		   const wxImage& di, const wxImage& ti, const wxImage& hi,
		   int is, const vector<wxImage>& icons):
  fits(f),display_colorspace(cs),cdatafile(cd),
  default_icon(di), table_icon(ti), head_icon(hi),icon_size(is)
{
  // create icons for all HDUs
  for(size_t k = 0; k < fits.HduCount(); k++) {

    switch(fits.Hdu(k).Type()) {
    case HDU_IMAGE:
      list.push_back(ImageIcon(icons[k]));
      break;

    case HDU_HEAD:
      list.push_back(SymbolIcon(head_icon,icon_size,icon_size));
      break;

    case HDU_TABLE:
      list.push_back(SymbolIcon(table_icon,icon_size,icon_size));
      break;

    default:
      list.push_back(default_icon);
      break;
    }
  }

  // cover icon
  switch(fits.Type()) {
  case FITS_GRAY:
  case FITS_COLOUR:
    wxASSERT(fits.HasImage() &&
	     fits.Hdu(0).Type() == HDU_IMAGE && fits.Hdu(0).IsOk());
    icon = list[0];
    break;

  case FITS_MULTI:
    icon = MultiIcon(list);
    break;

  default:
    icon = default_icon;
    break;
  }
}

MuniIcon::MuniIcon(const FitsFile& f, const wxString& cs, const wxString& cd,
		   const wxImage& di, const wxImage& ti, const wxImage& hi,
		   int is):
  fits(f),display_colorspace(cs),cdatafile(cd),
  default_icon(di), table_icon(ti), head_icon(hi),icon_size(is)
{
  // create icons for all HDUs
  for(size_t k = 0; k < fits.HduCount(); k++) {

    switch(fits.Hdu(k).Type()) {
    case HDU_IMAGE:
      list.push_back(ImageIcon(FitsArray(fits.Hdu(k))));
      break;

    case HDU_HEAD:
      list.push_back(SymbolIcon(head_icon,icon_size,icon_size));
      break;

    case HDU_TABLE:
      list.push_back(SymbolIcon(table_icon,icon_size,icon_size));
      break;

    default:
      list.push_back(default_icon);
      break;
    }
  }

  // cover icon
  switch(fits.Type()) {
  case FITS_GRAY:
  case FITS_COLOUR:
    wxASSERT(fits.HasImage() &&
	     fits.Hdu(0).Type() == HDU_IMAGE && fits.Hdu(0).IsOk());
    icon = list[0];
    break;

  case FITS_MULTI:
    icon = MultiIcon(list);
    break;

  default:
    icon = default_icon;
    break;
  }
}

MuniIcon::~MuniIcon()
{
  list.clear();
}

wxImage MuniIcon::ImageIcon(const wxImage& icon) const
{
  wxASSERT(icon.IsOk());
  return Padding(icon,icon_size);
}


wxImage MuniIcon::ImageIcon(const FitsArray& a) const
{
  wxASSERT(a.IsOk());

  if( a.Flavour() == HDU_IMAGE_COLOUR ){
    FitsTone tone(a.Plane(1));
    FitsColor color(cdatafile,a);
    return ColorIcon(a,tone,color);
  }
  else if ( a.Flavour() == HDU_IMAGE_FRAME ) {
    FitsTone tone(a);
    return GrayIcon(a,tone);
  }
  else {
    wxFAIL_MSG("----- WARNING: Unreachable code.");
    return wxImage();
  }
}

wxImage MuniIcon::Padding(const wxImage& icon, int size) const
{
  // empty pixels are filled by transparency
  int w,h;
  float r = float(icon.GetWidth()) / float(icon.GetHeight());
  if( r > 1  ) {
    w = size;
    h = int(w / r);
  }
  else {
    h = size;
    w = int(h / r);
  }
  int i0 = 0;
  int i1 = w;
  int j0 = 0;
  int j1 = h;
  int d;
  if( w > icon.GetWidth() ) {
    d = wxMax((w - icon.GetWidth()) / 2, 0);
    i0 = d;
    i1 = w - d;
  }
  if( h > icon.GetHeight() ) {
    d = wxMax((h - icon.GetHeight()) / 2, 0);
    j0 = d;
    j1 = h - d;
  }
  unsigned char *alpha = (unsigned char *) malloc(w*h);
  unsigned char *p = alpha;
  for(int i = 0; i < w; i++)
    for(int j = 0; j < h; j++)
      if( (i < i0 || j < j0) || (i >= i1 || j >= j1) )
	*p++ = 0;
      else
	*p++ = 255;
  wxImage i(w,h);
  i.SetAlpha(alpha);
  i.Paste(icon,i0,j0);
  return i;
}

wxImage MuniIcon::GrayIcon(const FitsArray& a, const FitsTone& tone) const
{
  FitsImage image(a);
  FitsDisplay display(image);
  display.SetShrink(icon_size,icon_size);
  display.SetTone(tone);
  FitsBitmap b = display.GetImage();
  wxImage icon(b.GetWidth(),b.GetHeight(),b.NewTopsyTurvyRGB());
  return Padding(icon,icon_size);
}

wxImage MuniIcon::ColorIcon(const FitsArray& a, const FitsTone& tone,
			    const FitsColor& color) const
{
  FitsImage image(a);
  FitsDisplay display(image);
  display.SetShrink(icon_size,icon_size);
  display.SetTone(tone);
  FitsBitmap b = display.GetImage();
  wxImage icon(b.GetWidth(),b.GetHeight(),b.NewTopsyTurvyRGB());
  return Padding(icon,icon_size);
}

wxImage MuniIcon::MultiIcon(const std::vector<wxImage>& list) const
{
  wxBitmap canvas(icon_size,icon_size);
  wxMemoryDC mdc(canvas);
  if( mdc.IsOk() ) {

    mdc.SetBackground(*wxTRANSPARENT_BRUSH);
    mdc.SetPen(*wxLIGHT_GREY_PEN);

    for(int l = (int) list.size()-1; l >= 0; l--) {
      wxBitmap b(list[l]);
      int x = 10*l;
      int y = 10*l;
      mdc.DrawRectangle(x,y,b.GetWidth(),b.GetHeight());
      mdc.DrawBitmap(b,x+1,y+1);
    }

    wxString t;
    t.Printf("%d",int(list.size()));
    mdc.SetFont(*wxSMALL_FONT);
    wxSize s = mdc.GetTextExtent(t);
    int w = s.GetWidth() > s.GetHeight() ? s.GetWidth() : s.GetHeight() + 1;
    int x = icon_size - w - 1;

    mdc.SetBackground(*wxLIGHT_GREY_BRUSH);
    mdc.DrawRoundedRectangle(x,1,w,w,5.0);
    mdc.DrawText(t,x+w/2-s.GetWidth()/2,3);
  }
  return canvas.ConvertToImage();;

}

wxImage MuniIcon::GetIcon() const
{
  if( icon.IsOk() )
    return icon;
  else
    return default_icon;
}


std::vector<wxImage> MuniIcon::GetList() const
{
  return list;
}


wxImage MuniIcon::BrowserIcon(const wxImage& icon,
			      const int cwidth, const int cheight,
			      const wxString& text, const wxColour& bcolour)
{
  wxASSERT(icon.IsOk());
  if( icon.IsOk() ) {

    int width = icon.GetWidth();
    int height = icon.GetHeight();
    int xoff = (cwidth - width)/2;
    int yoff = (cheight - height)/2;
    int dx = 3;
    int dy = 3;

    wxImage temp(cwidth,cheight);

    wxBitmap bitmap(temp);
    wxMemoryDC dc(bitmap);
    wxGraphicsContext *gc = wxGraphicsContext::Create(dc);
    if( gc ) {

      // clear canvas
      gc->SetBrush(bcolour);
      gc->DrawRectangle(0,0,cwidth,cheight);

      // draw black rectangle
      gc->SetBrush(*wxBLACK);
      gc->DrawRectangle(xoff+dx,yoff+dy,width-2*dx,height-2*dy);

      delete gc;
    }
    dc.SelectObjectAsSource(wxNullBitmap);
    wxImage blur = bitmap.ConvertToImage();
    wxImage iblur(blur.Blur(2));

    wxBitmap bmp(iblur);
    wxMemoryDC dcc(bmp);
    wxGraphicsContext *gcc = wxGraphicsContext::Create(dcc);
    if( gcc ) {
      gcc->SetPen(*wxWHITE);
      gcc->SetBrush(*wxTRANSPARENT_BRUSH);
      gcc->DrawRectangle(xoff+dx-1,yoff+dy-1,width-2*dx,height-2*dy);
      wxImage img(icon.GetSubImage(wxRect(dx,dx,width-2*dx,height-2*dy)));
      gcc->DrawBitmap(wxBitmap(img),xoff+dx-1,yoff+dy-1,width-2*dx,height-2*dy);

      if( ! text.IsEmpty() ) {
	wxFont fn(*wxSMALL_FONT);
	gcc->SetFont(fn,*wxBLACK);
	double wt,ht,tt;
	gcc->GetTextExtent(text,&wt,&ht,&tt,&tt);
	gcc->SetPen(*wxTRANSPARENT_PEN);
	gcc->SetBrush(bcolour);
	gcc->DrawRoundedRectangle((cwidth-wt)/2-1,cheight-ht-1-dy,wt+4,ht+1,2);
	gcc->DrawText(text,(cwidth-wt)/2,cheight-ht-1-dy);
      }

      delete gcc;
    }
    dcc.SelectObjectAsSource(wxNullBitmap);

    return bmp.ConvertToImage();

  }
  else {

    int width = 22;
    int height = 22;
    int xoff = cwidth/2 - width/2;
    int yoff = cheight - height;
    int r2 = (height+width)/4;

    int npix = cwidth*cheight;
    unsigned char *rgb = (unsigned char *) malloc(3*npix);
    unsigned char *alpha = (unsigned char *) malloc(npix);

    // fill transparency
    for(int i = 0; i < npix; i++) alpha[i] = 0;

    // draw backround as circle
    for(int j = 0; j < height; j++)
      for(int i = 0; i < width; i++) {
	int x = xoff + i;
	int y = yoff + j;
	int n = x + y*cwidth;
	wxASSERT(n < npix);
	int i2 = i - height/2;
	int j2 = j - width/2;
	if( i2*i2 + j2*j2 < r2*r2 ) {
	  for(int k = 0; k < 3; k++) rgb[3*n+k] = 255;
	  alpha[n] = 255;
	}
      }

    wxImage temp(cwidth,cheight,rgb);
    temp.SetAlpha(alpha);

    wxBitmap icon(temp);
    wxMemoryDC dc(icon);
    wxGraphicsContext *gc = wxGraphicsContext::Create(dc);
    if( gc ) {
      // draw clock symbol
      wxColour colour(128,128,128);
      gc->SetBrush(gc->CreateBrush(wxBrush(*wxTRANSPARENT_BRUSH)));
      gc->SetPen(gc->CreatePen(wxPen(colour,2,wxSOLID)));
      gc->DrawEllipse(xoff+2,yoff+2,width-4,height-4);
      gc->SetPen(gc->CreatePen(wxPen(colour,1,wxSOLID)));
      delete gc;
    }

    dc.SelectObjectAsSource(wxNullBitmap);

    return icon.ConvertToImage();
  }

  // Important: the function should be invoked from
  // main thread only. The wxBitmap.ConvertToImage() calls under GTK+
  // function assigned directy to a window system (to a GC).

}

wxImage MuniIcon::DefaultIcon(int cwidth, int cheight)
{
  int width = cwidth;
  int height = int(width/1.618);

  int xcen = width/2;
  int ycen = height/2;

  // dimensions of symbol
  wxFont fn(*wxNORMAL_FONT);
  int ssize = fn.GetPointSize()+3;
  int wsym = 3*ssize/2;
  int hsym = wsym;

  wxColour hi(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
  wxColour bg(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
  bg = *wxLIGHT_GREY;
  unsigned char alpha(255);


  wxImage temp(width,height,false);

  wxBitmap icon(temp);
  wxMemoryDC dc(icon);
  dc.SetBrush(bg);
  dc.Clear();

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

    wxColour c(hi.Red(),hi.Green(),hi.Blue(),alpha);
    wxBrush b(wxColour(bg));

    gc->SetFont(*wxSMALL_FONT,bg);

    gc->SetBrush(c);
    gc->SetPen(wxPen(bg));

    gc->DrawRectangle(0,0,width,height);

    // text
    gc->DrawText(".. default ..",10,10);

    // pentagram
    double dx = wsym/2;
    double dy = hsym/2;
    double x1 = xcen - dx;
    double y1 = ycen - dy;
    gc->SetBrush(wxBrush(c,wxTRANSPARENT));
    gc->DrawEllipse(x1,y1,wsym,hsym);
    wxGraphicsPath gp(gc->CreatePath());
    gp.MoveToPoint(xcen,ycen+dy);
    gp.AddLineToPoint(xcen-dx/2,ycen+dy/2);
    gp.AddLineToPoint(xcen+dx,ycen+dy/2);
    gp.AddLineToPoint(xcen+dx,ycen);
    gc->StrokePath(gp);

    delete gc;
  }

  dc.SelectObjectAsSource(wxNullBitmap);

  return icon.ConvertToImage();
}


wxImage MuniIcon::SymbolIcon(const wxImage& symbol, int width, int height)
{
  wxASSERT( width > 0 && height > 0);
  wxBitmap canvas(width,height);
  wxMemoryDC image(canvas);
  if( image.IsOk() ) {
    image.SetBrush(wxBrush(*wxWHITE_BRUSH));
    image.DrawRectangle(0,0,width,height);
    image.DrawBitmap(wxBitmap(symbol.Scale(width-20,height-10)),10,5);
  }
  return canvas.ConvertToImage();
}

wxImage MuniIcon::BulletIcon(const wxSize& size, const wxColour& c)
{
  int x0 = size.GetWidth()/2;
  int y0 = size.GetHeight()/2;
  int radius = 20;

  wxColour colour(c.Red(),c.Green(),c.Blue(),255);
  int npix = size.GetWidth()*size.GetHeight();
  unsigned char *rgb = (unsigned char *) malloc(3*npix);

  wxColour cb = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
  wxImage image(size.GetWidth(),size.GetHeight(),rgb);
  for(int i = 0; i < image.GetWidth(); i++)
    for(int j = 0; j < image.GetHeight(); j++)
      image.SetRGB(i,j,cb.Red(),cb.Green(),cb.Blue());

  wxBitmap bitmap(image);
  wxMemoryDC dc(bitmap);
  wxGraphicsContext *gc = wxGraphicsContext::Create(dc);
  if( gc ) {
    gc->SetPen(wxPen(colour));
    gc->SetBrush(wxBrush(colour));
    wxGraphicsBrush b =
      gc->CreateRadialGradientBrush(x0-radius/8,y0-radius/8,x0+radius/8,y0+radius/8,
				    3*radius/5,*wxWHITE,colour);
    gc->SetBrush(b);
    gc->DrawEllipse(x0-radius/2,y0-radius/2,radius,radius);
    delete gc;
  }
  dc.SelectObjectAsSource(wxNullBitmap);
  return bitmap.ConvertToImage();
}


wxImage MuniIcon::ListIcon(const wxImage& icon, int size, const wxColour& c)
{
  wxASSERT(icon.IsOk() && icon.GetHeight() > 0);
  wxSize s;
  wxPoint p;
  double r = double(icon.GetWidth()) / double(icon.GetHeight());
  if( icon.GetWidth() > icon.GetHeight() ) {
    float x = r > 0.0 ? size/r : 1;
    if( x < 1 ) x = 1;
    s = wxSize(size,int(x));
    p = wxPoint(0,(size-s.GetHeight())/2);
  }
  else {
    float x = size*r;
    if( x < 1 ) x = 1;
    s = wxSize(int(x),size);
    p = wxPoint((size-s.GetWidth())/2,0);
  }
  wxImage img = icon.Scale(s.GetWidth(),s.GetHeight(),wxIMAGE_QUALITY_HIGH);

  for(int i = 0; i < img.GetWidth(); i++)
    for(int j = 0; j < img.GetHeight(); j++) {
      unsigned char r = (c.Red()*img.GetRed(i,j))/255;
      unsigned char g = (c.Green()*img.GetGreen(i,j))/255;
      unsigned char b = (c.Blue()*img.GetBlue(i,j))/255;
      img.SetRGB(i,j,r,g,b);
    }

  return img.Size(wxSize(size,size),p);
}
