#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Shared_Image.H> // Image I/O
#include <FL/filename.H>        // fl_filename_name

#include <string.h>             // strncmp, strlen, etc.
#include <stdio.h> 
#include <fstream>

#include "ui_misc.h"
#include "url_decode.h"

#include "image.h"
// #include "png.hpp"


#define SO 1000 // scroll offset (fl scroll doesn't report negative values)
#define ZOOM_MAX 4

#include "../util.h"

// constructor / destructor
ImageBox::ImageBox(int X, int Y, int W, int H, const char *L)
    : Fl_Box(X,Y,W,H,L)
{
  zoom_level = 1;
  image_is_png = false;
  box(FL_FLAT_BOX);
  //color(FL_BLACK);
  current_filename = "";
  lockGuides = false;
  lastGuides = -1;
  {
    scrollArea = new ScrollArea(X,Y,W,H);
    scrollArea->align(FL_ALIGN_INSIDE | FL_ALIGN_CENTER);
    scrollArea->type(0);  // no scroll bars
    //scrollArea->label(L);
    scrollArea->begin();
    {
      Fl_Box *anchor = new Fl_Box(X,Y,0,0); // so the scroller doesn't forget its offset
      anchor->label(0); // so gcc doesn't warn about unused variable
      
      crossBox = new CrossBox(SO+X, SO+Y, 480, 300, 0);
      //crossBox->color(fl_rgb_color(40,40,40));
      // crossBox->color(FL_BLACK);
      // crossBox->box(FL_FLAT_BOX);
      crossBox->align(FL_ALIGN_INSIDE | FL_ALIGN_CENTER);
      crossBox->owner = this;
      
      
      imgBox = new DragBox(SO+W/2, SO+H/2, 0, 0, 0);
      imgBox->align(FL_ALIGN_INSIDE | FL_ALIGN_CENTER);
      imgBox->owner = this;
    }
    scrollArea->end();
    scrollArea->position(SO, SO);
  }
  clear_visible_focus();
  center();
}

ImageBox::~ImageBox()
{
  file_close();
}



// center the crosshair within the image box.
void ImageBox::center()
{
  
  if (!imgBox->w())
  {
    scrollArea->position(SO+240- w()/2, SO+150- h()/2);
    return;
  }
  
  // bool neg = image_x() <= 0 && image_y() <= 0;
  bool neg;
  if (lockGuides) neg = lastGuides < 1;
  else neg = image_x() <= 0 && image_y() <= 0;
    
  
  int xpos = (SO + (!neg?160* zoom_level:320* zoom_level)) - w()/2;
  int ypos = (SO + (!neg?100* zoom_level:200* zoom_level)) - h()/2;
  
  scrollArea->position(xpos, ypos);
  scrollArea->redraw();
}

void ImageBox::resize(int _x, int _y, int _w, int _h)
{
  Fl_Box::resize(_x, _y, _w, _h);
  center();
}

// get origin coordinates

int ImageBox::origin_x()
{
  return crossBox->x() + 320* zoom_level / 2;
}
int ImageBox::origin_y()
{
  return crossBox->y() + 200* zoom_level / 2;
}

// get position of image relative to origin

int ImageBox::image_x()
{
  img_x = ( imgBox->x() - origin_x() )  / - zoom_level ;
  return img_x;
}

int ImageBox::image_y()
{
  img_y = ( imgBox->y() - origin_y() )  / - zoom_level ;
  return img_y;
}

// set position of image relative to origin

void ImageBox::image_x(int val)
{
  imgBox->position(origin_x()-val* zoom_level ,imgBox->y());
  img_x = val;
  parent()->redraw();
}

void ImageBox::image_y(int val)
{
  imgBox->position(imgBox->x(),origin_y()-val* zoom_level);
  img_y = val;
  parent()->redraw();
}




void ImageBox::zoom(int z)
{
  z = ( z < 1 ? 1 : ( z > ZOOM_MAX ? ZOOM_MAX : z ) );
  if (z == zoom_level) return;
  zoom_level = z;
  crossBox->size(480 * z, 300 * z);
  imgBox->size(img_w* z, img_h* z);
  imgBox->fit_image();
  image_x(img_x);
  image_y(img_y);
  scrollArea->redraw();
  center();
}







int ImageBox::handle(int e)
{
  switch(e)
  {
    case FL_DND_ENTER:
    case FL_DND_RELEASE:
    case FL_DND_LEAVE:
    case FL_DND_DRAG:
      return 1;

    case FL_RELEASE:
      break;
    
    case FL_PASTE:
      dnd_open(Fl::event_text(), Fl::event_length());
      return 1;
    
    case FL_MOUSEWHEEL:
      if (current_filename.length() == 0) 
        return 1; 
      if (Fl::event_dy() > 0) 
        zoom(zoom_level-1);
      else 
        zoom(zoom_level+1);
      return 1;
  }

  return Fl_Box::handle(e);
}



void ImageBox::file_save()
{
  
  if (current_filename.length() == 0)
  {
    fprintf(stderr,"Nothing to save.\n");
    return;
  }
  
  if (!image_is_png) 
  {
    create_png();
    return;
  }
  
  const char *fn = current_filename.c_str();
  
  
  PngGrabber::staticGrab(fn, image_x(), image_y());
  PngGrabber::staticAlph(fn, image_alph);
  
  printf("Updated %s(%i,%i)[%i]\n",fn, image_x(), image_y(),image_alph);
  
}


// create a png file
void ImageBox::create_png()
{
  std::string pngpath = current_filename + ".png";
  const char *fn = pngpath.c_str();
  Fl_Shared_Image *img = Fl_Shared_Image::get(current_filename.c_str());
  
 //printf("making png\n");
  
  if (!ImageHandler::save(img, fn)) return;
  img->release();
  
  //printf("done making png\n");
  
  PngGrabber::staticGrab(fn, image_x(), image_y());
  PngGrabber::staticAlph(fn, image_alph);
  printf("Created %s(%i,%i)[%i]\n",fn, image_x(), image_y(),image_alph);
  
  file_open(fn);
  
}
  

void ImageBox::file_close()
{
  imgBox->close_image((Fl_Shared_Image *)imgBox->image());
  imgBox->image(0);
}

bool ImageBox::file_open(const char *fpath)
{
  
  if(strlen(fpath) == 0)
  {
    return false;
  }
  
  if(!fileExists((char *)fpath) )
  {
    return false;
  }
  
  if(current_filename == fpath)
  {
    file_close();
  }
  
  Fl_Shared_Image *img;
  
  PngGrabber::PngInfo info = PngGrabber::getInfo(fpath);
  
  if (info.ok)  // if it's a png, use pngpp to load it (fltk/win32 screws up gray+alpha pngs)
  {
    png::image< png::rgba_pixel > image(fpath);
    int ih = image.get_height();
    int iw = image.get_width();
    uchar *buf = new uchar[ih * iw * 4];
    
    for(int y=0; y<ih; y++) for(int x=0; x<iw; x++)
    {
      png::rgba_pixel pix  = image.get_pixel(x,y);
      uchar *p = (uchar *)(&pix);
      buf[y * iw * 4 + x * 4 + 0] = *p;
      buf[y * iw * 4 + x * 4 + 1] = *(p+1);
      buf[y * iw * 4 + x * 4 + 2] = *(p+2);
      buf[y * iw * 4 + x * 4 + 3] = *(p+3);
    }
    
    Fl_RGB_Image rgb_img(&buf[0], iw, ih, 4);
    img = (Fl_Shared_Image *)(rgb_img.copy());
    
    delete [] buf;
    //delete rgb_img;
    // img = rimg;
  }
  else
  {
    img = Fl_Shared_Image::get(fpath);
  }
  
  if(!img)
  {
    fprintf(stderr, "Can't load image: %s\n", fpath);
    return false;
  }
  file_close();
  
  current_filename = fpath;
  label(0);
  
  imgBox->image(img);
  img_w = img->w();
  img_h = img->h();
  imgBox->size(img->w()* zoom_level, img->h()* zoom_level);
  imgBox->fit_image();
  //imgBox->image(img->copy(imgBox->w(), imgBox->h()));
  // printf("Loaded image: %s \n", fpath);
  
  
  image_is_png = info.ok;
  
  image_x(info.x); image_y(info.y);
  image_alph = info.alph;

  Enums::ActionType a = Enums::gpLOAD;
  
  do_callback(this, (void*)&a);
  
  Fl::redraw();
  
  return true;
}

void ImageBox::color(Fl_Color c)
{
  Fl_Box::color(c);
  imgBox->color(c);
  crossBox->color(c);
  scrollArea->color(c);
}

// Assume pairs of bytes: [char] [nul] [char] [nul] .. [nul] [nul]
void multibyte_convert(const char *in, char *out)
{
     for ( *out = *in; *in; )
        {  out++; in += 2; *out = *in; }
}

void ImageBox::dnd_open(const char *urls_in, size_t length)
{
  
  if ( urls_in == 0 || length == 0 ) return;

  // printf("Dragged %s\n", urls);

  
 char *urls = new char [length+1];
  
  // Check for multibyte chars from Firefox/linux.
  //    Length will be >1 and second byte will be zero.
  if ( strlen(urls_in) != length && length > 1 && urls_in[1] == 0 )
    { multibyte_convert(urls_in, urls); }
  else
    { strcpy(urls, urls_in); }

  
  int pathLen = strlen(urls);
  
  int p = 0;
  for(const char *c = urls; *c != '\0'; ++c)
  {
    if(*c == '\n')
    {
      pathLen = p-1;
      break;
    }
    p++;
  }
  
  int frontstrip = 0;
  
  if(strncmp(urls, "file://", 7) == 0)
  {
    frontstrip = 7;
  }
  else if(urls[1]==':')
  {
    frontstrip = 0;
  }
  else
  {
    fprintf(stderr, "Doesn't look like a local file (%s).\n",urls);
    return;
  }

  // Fl::event_text() gives URLs in the format: file:///path/to/image\r\n
  int fsz = pathLen - frontstrip;
  char *fpath = new char[fsz+1];
  strncpy(fpath, urls+frontstrip, fsz);
  fpath[fsz] = '\0';
  
  if (file_open(url_decode(fpath))) center();
    
  delete [] fpath;
}





const char *ImageBox::base_filename()
{
  return fl_filename_name(current_filename.c_str());
}

void ImageBox::file_next(bool bw)
{
  dirent** DirList;
  
  std::string path =  current_filename ;
  std::string base_fn = base_filename();
  int pathlen = path.length() - base_fn.length();
  
  path.resize(pathlen);

  int size = fl_filename_list(path.c_str(), &DirList);
  
  bool gotMatch = false;
  bool pass2 = false;
  
  // loop over all files in the current image's directory.
  for ( int f = (!bw ? 0 : size-1); 
      (!bw ? f <= size : f >= -1); 
      (!bw ? f++ : f--) )
  {
    // wrap around for another pass if at end
    if (f == (!bw ? size : -1)) 
    {
      if (gotMatch && !pass2)
      { 
        f = (!bw ? 0 : size-1);
        pass2 = true;
      }
      else return;
    }
    // get file name
    const char *dname = DirList[f]->d_name; 
    if (base_fn == dname && pass2) return;
    if (gotMatch) //  if we found the current image..
    {
      // ignore subdirectories
      if (dname[strlen(dname)-1]=='/') continue;
      // try to open the file as an image
      if (file_open((path + dname).c_str())) return;
    }
    // see if we found the current image
    if (base_fn == dname) gotMatch = true;
  }

}
