#include "oily_png_ext.h"

void oily_png_check_size_constraints(long self_width, long self_height, long other_width, long other_height, long offset_x, long offset_y){
  // For now, these raise a standard runtime error. They should however raise custom exception classes (OutOfBounds)
  if(self_width  < other_width  + offset_x){
    rb_raise(rb_eRuntimeError, "Background image width is too small!");
  }
  if(self_height < other_height + offset_y){
    rb_raise(rb_eRuntimeError, "Background image height is too small!");
  }
}

VALUE oily_png_compose_bang(int argc, VALUE *argv, VALUE self) {
  // Corresponds to the other image(foreground) that we want to compose onto this one(background).
  VALUE other;
  
  // The offsets are optional arguments, so these may or may not be null pointers.
  // We'll prefix them with 'opt' to identify this.
  VALUE opt_offset_x;
  VALUE opt_offset_y;
  
  // Scan the passed in arguments, and populate the above-declared variables. Notice that '12'
  // specifies that oily_png_compose_bang takes in 1 required parameter, and 2 optional ones (the offsets)
  rb_scan_args(argc, argv, "12", &other,&opt_offset_x,&opt_offset_y);
  
  // Regardless of whether offsets were provided, we must specify a default value for them since they will
  // be used in calculating the position of the composed element.
  long offset_x = 0;
  long offset_y = 0;

  // If offsets were provided, then the opt_offset_* variables will not be null pointers. FIXNUM_P checks
  // whether they point to a fixnum object. If they do, then we can safely assign our offset_* variables to the values.
  if(FIXNUM_P(opt_offset_x)){
    offset_x = FIX2LONG(opt_offset_x);
  }
  if(FIXNUM_P(opt_offset_y)){
    offset_y = FIX2LONG(opt_offset_y);
  }
  
  // Get the dimension data for both foreground and background images.
  long self_width        = FIX2LONG(rb_funcall(self, rb_intern("width"), 0));
  long self_height       = FIX2LONG(rb_funcall(self, rb_intern("height"), 0));
  long other_width       = FIX2LONG(rb_funcall(other, rb_intern("width"), 0));
  long other_height      = FIX2LONG(rb_funcall(other, rb_intern("height"), 0));
  
  // Make sure that the 'other' image fits within the current image. If it doesn't, an exception is raised
  // and the operation should be aborted.
  oily_png_check_size_constraints( self_width, self_height, other_width, other_height, offset_x, offset_y );
  
  // Get the pixel data for both the foreground(other) and background(self) pixels. 
  VALUE* bg_pixels = RARRAY_PTR(rb_funcall(self, rb_intern("pixels"), 0));
  VALUE* fg_pixels = RARRAY_PTR(rb_funcall(other, rb_intern("pixels"), 0));

  long x = 0;
  long y = 0;
  long bg_index = 0; // corresponds to the current index in the bg_pixels array.
  for( y = 0; y < other_height; y++ ){
    for( x = 0; x < other_width; x++ ){
      // We need to find the value of bg_index twice, so we only calculate and store it once.
      bg_index = ( x + offset_x ) + ( y + offset_y ) * self_width;
      // Replace the background pixel with the composition of background + foreground
      bg_pixels[bg_index] = UINT2NUM( oily_png_compose_color( NUM2UINT( fg_pixels[x+ y * other_width] ), NUM2UINT( bg_pixels[bg_index] ) ) );
    }
  }
  return self;
}


VALUE oily_png_replace_bang(int argc, VALUE *argv, VALUE self) {
  // Corresponds to the other image(foreground) that we want to compose onto this one(background).
  VALUE other;
  
  // The offsets are optional arguments, so these may or may not be null pointers.
  // We'll prefix them with 'opt' to identify this.
  VALUE opt_offset_x;
  VALUE opt_offset_y;
  
  // Scan the passed in arguments, and populate the above-declared variables. Notice that '12'
  // specifies that oily_png_compose_bang takes in 1 required parameter, and 2 optional ones (the offsets)
  rb_scan_args(argc, argv, "12", &other,&opt_offset_x,&opt_offset_y);
  
  // Regardless of whether offsets were provided, we must specify a default value for them since they will
  // be used in calculating the position of the composed element.
  long offset_x = 0;
  long offset_y = 0;

  // If offsets were provided, then the opt_offset_* variables will not be null pointers. FIXNUM_P checks
  // whether they point to a fixnum object. If they do, then we can safely assign our offset_* variables to the values.
  if(FIXNUM_P(opt_offset_x)){
    offset_x = FIX2LONG(opt_offset_x);
  }
  if(FIXNUM_P(opt_offset_y)){
    offset_y = FIX2LONG(opt_offset_y);
  }
  
  // Get the dimension data for both foreground and background images.
  long self_width        = FIX2LONG(rb_funcall(self, rb_intern("width"), 0));
  long self_height       = FIX2LONG(rb_funcall(self, rb_intern("height"), 0));
  long other_width       = FIX2LONG(rb_funcall(other, rb_intern("width"), 0));
  long other_height      = FIX2LONG(rb_funcall(other, rb_intern("height"), 0));
  
  // Make sure that the 'other' image fits within the current image. If it doesn't, an exception is raised
  // and the operation should be aborted.
  oily_png_check_size_constraints( self_width, self_height, other_width, other_height, offset_x, offset_y );
  
  // Get the pixel data for both the foreground(other) and background(self) pixels. 
  VALUE* bg_pixels = RARRAY_PTR(rb_funcall(self, rb_intern("pixels"), 0));
  VALUE* fg_pixels = RARRAY_PTR(rb_funcall(other, rb_intern("pixels"), 0));

  long x = 0;
  long y = 0;
  long bg_index = 0; // corresponds to the current index in the bg_pixels array.
  for( y = 0; y < other_height; y++ ){
    for( x = 0; x < other_width; x++ ){
      // We need to find the value of bg_index twice, so we only calculate and store it once.
      bg_index = ( x + offset_x ) + ( y + offset_y ) * self_width;
      // Replace the background pixel with the composition of background + foreground
      bg_pixels[bg_index] = fg_pixels[x+ y * other_width];
    }
  }
  return self;
}

VALUE oily_png_rotate_left_bang(VALUE self){
  int store_at;
  VALUE pixel_value;
  int canvas_width = NUM2INT(rb_funcall(self, rb_intern("width"), 0));
  int canvas_height = NUM2INT(rb_funcall(self, rb_intern("height"), 0));
  VALUE original_pixels = rb_funcall(self, rb_intern("pixels"), 0);
  VALUE new_pixels = rb_ary_dup(original_pixels);
  int i, j;
  for( j = 0 ; j < canvas_width; j++ ){
    for( i = 0 ; i < canvas_height; i++ ){
      store_at = (canvas_width - 1 - j)*canvas_height + i;
      pixel_value = rb_ary_entry(original_pixels, i*canvas_width + j);
      rb_ary_store(new_pixels, store_at, pixel_value );
    }
  }
  rb_funcall(self, rb_intern("replace_canvas!"), 3, INT2NUM(canvas_height), INT2NUM(canvas_width), new_pixels);
  return self;
}

VALUE oily_png_rotate_right_bang(VALUE self){
  int store_at;
  VALUE pixel_value;
  int canvas_width = NUM2INT(rb_funcall(self, rb_intern("width"), 0));
  int canvas_height = NUM2INT(rb_funcall(self, rb_intern("height"), 0));
  VALUE original_pixels = rb_funcall(self, rb_intern("pixels"), 0);
  VALUE new_pixels = rb_ary_dup(original_pixels);
  int i, j;
  for( j = 0; j < canvas_width; j++ ){
    for( i = 0; i < canvas_height; i++ ){
      store_at = j * canvas_height + (canvas_height - i - 1);
      pixel_value = rb_ary_entry(original_pixels, i*canvas_width + j);
      rb_ary_store(new_pixels, store_at, pixel_value );
    }
  }
  rb_funcall(self, rb_intern("replace_canvas!"), 3, INT2NUM(canvas_height), INT2NUM(canvas_width), new_pixels);
  return self;
}