/* Copyright (c) 2009, Neil Birkbeck      

   Real quick hack attempt at an image processing filter.  Turns image
   into rectangles that are rotated to align with image gradient.  Also
   adds some edges.
   
   Author: Neil Birkbeck
*/

#include <stdio.h>
#include <nimage/image.h>
#include <nmath/vec3.h>
#include <cairo/cairo.h>
#include <assert.h>
#include <vector>
#include "shocker-lib.h"


using namespace nacb;

struct Rectangle {
  double angle;
  double  x, y;
  double scale;
  Vec3d  fillColor;
  Vec3d  strokeColor;
    
  Rectangle(){
    fillColor = Vec3d(0.5, 0.5, 0.5);
    strokeColor = Vec3d(0, 0, 0);
  }

  void draw(cairo_t * cairo, double blockSize){
    // Colors are swizzled (as format as RGB24)
    cairo_set_source_rgb(cairo, fillColor.z, fillColor.y, fillColor.x);
    cairo_identity_matrix(cairo);
    cairo_translate(cairo, x, y);
    cairo_rotate(cairo, angle);
    cairo_scale(cairo, scale, scale);
      
    cairo_rectangle(cairo, -blockSize/2, -blockSize/2, blockSize, blockSize);
      
    cairo_set_line_width(cairo, 0.5/scale);
    cairo_fill_preserve(cairo);
      
    cairo_set_source_rgb(cairo, strokeColor.z, strokeColor.y, strokeColor.x);
    cairo_stroke(cairo);
  }

  bool operator<(const Rectangle & other) const {
    return scale < other.scale;
  }
};


nacb::Imagef rectangle(const nacb::Imagef & input, int blockSize = 12){ 
  std::vector<Rectangle> rects;
  nacb::Image8 result(input.w, input.h, 4);
  nacb::Imagef st = structure_tensor(input);
  st = gauss_filter(st, 2, 6);

  nacb::Imagef dir = dominant(st);

  result = 1;

  cairo_surface_t * surface = 
    cairo_image_surface_create_for_data(result.data, CAIRO_FORMAT_RGB24, 
					result.w, result.h, result.w*sizeof(int));
  
  cairo_t * cairo = cairo_create(surface);

  nacb::Imagef imd = input.resize(input.w/blockSize, input.h/blockSize);

  dir = dir.resize(input.w/blockSize, input.h/blockSize);
  
  nacb::Imagef ddir = directional_deriv(imd, dir);

  cairo_set_source_rgb(cairo, 0.5, 0.5, 0.5);
  cairo_rectangle(cairo, 0, 0, result.w, result.h);
  cairo_fill(cairo);

  for(int y=0; y<input.h; y+=blockSize){
    for(int x=0; x<input.w; x+=blockSize){
      double angle = atan2(dir(x/blockSize, y/blockSize, 1), 
			   dir(x/blockSize, y/blockSize, 0));

      if(isnan(angle) || isinf(angle))
	angle = 0;

      // random scale.
      double sc = (std::min(2.0, 3.0*sqrt(st(x, y, 0) + st(x, y, 1) + st(x, y, 2)) + 0.8));
      int xlo = std::min(imd.w-1, x/blockSize), ylo = std::min(imd.h-1, y/blockSize);

      Rectangle r;
      r.x = x + blockSize/2;
      r.y = y + blockSize/2;
      r.scale = sc;
      r.angle = angle;
      r.fillColor = Vec3d( imd(xlo, ylo, 0), 
			   imd(xlo, ylo, 1),
			   imd(xlo, ylo, 2));
      rects.push_back(r);

      /*
      //cairo_identity_matrix(cairo);
      cairo_new_path(cairo);
      cairo_move_to(cairo, 0, 0);
      cairo_line_to(cairo, -x, -y);
      cairo_line_to(cairo, x + 10, y + 10);
      cairo_line_to(cairo, x, 0);
      
      //cairo_close_path(cairo);
      cairo_stroke(cairo);
      */
    }
  }

  std::sort(rects.begin(), rects.end());

  for(int i=0; i<rects.size(); i++){
    rects[i].draw(cairo, blockSize);
  }


  // This looks like shit.
  for(int y=0; y<input.h; y+=blockSize){
    for(int x=0; x<input.w; x+=blockSize){
      double angle = atan2(dir(x/blockSize, y/blockSize, 1), 
			   dir(x/blockSize, y/blockSize, 0));
      
      if(isnan(angle) || isinf(angle))
	angle = 0;

      int xlo = std::min(imd.w-1, x/blockSize), 
	ylo = std::min(imd.h-1, y/blockSize);

      if(fabs(ddir(xlo, ylo, 0)) > 0.1){
	cairo_identity_matrix(cairo);
	cairo_translate(cairo, x + blockSize/2, y + blockSize/2);
	cairo_rotate(cairo, angle);

	double scale = std::min(1.25, fabs(ddir(xlo, ylo)/0.2));
	cairo_scale(cairo, scale, scale*blockSize);
	
	cairo_set_source_rgba(cairo, 0, 0, 0, scale/1.25);
	cairo_arc(cairo, 0, 0, 1, -M_PI, M_PI);
	cairo_fill(cairo);
      }
    }
  }


  //cairo_surface_finish(surface);
  cairo_destroy(cairo);
  cairo_surface_flush(surface);

  //cairo_status_t stat = cairo_surface_write_to_png(surface, "/tmp/output.png");
  cairo_surface_destroy(surface);

  nacb::Imagef r;
  r = result;
  return r;
}


int main(int ac, char * av[]){
  if(ac<=2){
    printf("Need input file and output file.\n");
    return 0;
  }

  nacb::Imagef image(av[1]);
  nacb::Imagef result = rectangle(image, ac>=3 ? atoi(av[2]): 16);

  if(ac>=4) {
    printf("writing: %s\n", av[3]);moustache-10
    result.write(av[3]);
  }
  return 0;
}
