//==============================================
//  copyright            : (C) 2003-2005 by Will Stokes
//==============================================
//  This program 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 2 of the License, or
//  (at your option) any later version.
//==============================================

//Systemwide includes
#include <qimage.h>
#include <qstring.h>
#include <cstdlib>
#include <time.h>

//Projectwide includes
#include "pointillism.h"
#include "blackWhite.h"
#include "manipulationOptions.h"

//----------------------------------------------
// Inputs:
// -------
// QString filename   - location of original image on disk
// StatusWidget* status - widget for making progress visible to user
//
// Outputs:
// --------
// QImage* returned - constructed image
//
// Description:
// ------------
// This method constructs an image using a pointillism approach using luminance
// data from the original image.
//
// This effect is under heavy development and will be documented 
// in full when it is complete.
//----------------------------------------------

//==============================================
void pickRandomPixelWithinBlock( int width, int height, 
                                 int blockX, int blockY,
                                 int BLOCK_SIZE,
                                 int &x, int &y )
{
  int dx = rand() % BLOCK_SIZE;
  int dy = rand() % BLOCK_SIZE;
  x = blockX*BLOCK_SIZE + dx;
  y = blockY*BLOCK_SIZE + dy;
  
  if(x < 0) x = 0;
  if(y < 0) y = 0;
  if(x >= width ) x = width-1;
  if(y >= height) y = height-1;  
}
//----------------------------------------------
bool pixelValid( QImage* image, int x, int y )
{
  return (
          x >= 0 &&
          y >= 0 &&
          x < image->width() &&
          x < image->height() );
}
//----------------------------------------------
double computeLocalGrayVal( QImage* image, int x, int y )
{
  int weights[3][3] = { {1,2,1}, {2,4,2}, {1,2,1} };
  
  int divisorSum = 0;
  double sum = 0;
  int xp, yp;
  for(yp = QMAX( y-1, 0); yp < QMIN( image->height()-1, y+1 ); yp++)
  {
    uchar* scanLine = image->scanLine(yp);
    
    for(xp = QMAX( x-1, 0); xp< QMIN( image->width()-1, x+1 ); xp++)
    {
      //compute dx and dy values
      int dx = xp - x;
      int dy = yp - y;
      
      //compute weight index
      int weightX = dx+1;
      int weightY = dy+1;
      
      //update sum and divisor count      
      sum+= (weights[weightX][weightY] * qGray( *((QRgb*)scanLine+xp) ) );
      divisorSum+= weights[weightX][weightY];
    }
  }
  
  //return weighted average  
  return sum/divisorSum; 
}
//----------------------------------------------
void drawDotAt( QImage* image, int x, int y, int )
{
  //TODO: antialias over grid, for now
  //just update this pixel value
  uchar* scanLine = image->scanLine(y);
  QRgb* rgb = ((QRgb*)scanLine+x);
  int red = qRed(*rgb);
  red = (int) (0.6*red);
  *rgb = qRgb( red, red, red);
}
//----------------------------------------------
QImage* pointillismEffect( QString filename, ManipulationOptions* )
{
  //intialize seed using current time
  srand( unsigned(time(NULL)) );

  //load original image and convert to grayscale
  QImage* originalImage = blackWhiteEffect( filename, NULL );
  
  //construct edited image
  QImage* editedImage = new QImage( originalImage->width(),
                                    originalImage->height(),
                                    originalImage->depth() );

  //fill with white since we'll be drawing black/color dots on top
  editedImage->fill( qRgb(255,255,255) );

  //break image into BLOCK_SIZE x BLOCK_SIZE blocks. iterate over
  //each block and pick a random pixel within. Local
  //average gray value in edited image is > originalImage + thresh
  //then draw a dot at pixel. continue doing this for each block
  //and repeat until ???
  const int BLOCK_SIZE = 8;

  //compute image size in blocks
  int blocksWide = editedImage->width() / BLOCK_SIZE;
  if(blocksWide*BLOCK_SIZE < editedImage->width())
  { blocksWide++; }
  
  int blocksTall = editedImage->height() / BLOCK_SIZE;
  if(blocksTall*BLOCK_SIZE < editedImage->height())
  { blocksTall++; }

  //iterate over image say 100 times, we'll need to fix this outer loop to be smarter?
  int bx,by,x,y;
  for(int i=0; i<10; i++)
  {
    //iterate over all blocks
    for(bx=0; bx<blocksWide; bx++)
    {
      for(by=0; by<blocksTall; by++)
      {
        //pick random pixel within block
        pickRandomPixelWithinBlock( editedImage->width(),
                                    editedImage->height(),
                                    bx, by,
                                    BLOCK_SIZE,
                                    x, y );
        
        double curGrayVal = computeLocalGrayVal( editedImage, x, y );
        double goalGrayVal = computeLocalGrayVal( originalImage, x, y );

        //too bright -> draw dot
        if( curGrayVal > goalGrayVal )
        { drawDotAt( editedImage, x, y, 5 ); }
      }
    }
  }

  //free grayscale form of original image
  delete originalImage;
  originalImage = NULL;
  
  //return pointer to edited image
  return editedImage;      
}
//==============================================
