// Copyright 2002, 2003, 2004 David Hilvert <dhilvert@auricle.dyndns.org>,
//                                          <dhilvert@ugcs.caltech.edu>

/*  This file is part of the Anti-Lamenessing Engine.

    The Anti-Lamenessing Engine 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.

    The Anti-Lamenessing Engine 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 the Anti-Lamenessing Engine; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/*
 * image_ale_real.h: Image represented by an array of ale_reals
 */

#ifndef __image_ale_real_h__
#define __image_ale_real_h__

#include "exposure/exposure.h"
#include "point.h"
#include "image.h"

#define RESIDENT_DIVISIONS 200


template <int disk_support>
class image_ale_real : public image {
private:
	/*
	 * Data structures without file support.
	 */

	spixel *_p;

	/*
	 * Data structures for file support.
	 */

	FILE *support;
	mutable spixel *_p_segments[RESIDENT_DIVISIONS];
	mutable int dirty_segments[RESIDENT_DIVISIONS];
	mutable int resident_list[RESIDENT_DIVISIONS];
	mutable int resident_next;
	int resident_max;
	int rows_per_segment;
	mutable thread::rwlock_t rwlock;

public:

	/*
	 * Wrapper encapsulating details of the separation between the
	 * resident-checking implementation and non-checking.
	 */

	static image *new_image_ale_real(unsigned int dimy,
					 unsigned int dimx,
					 unsigned int depth,
					 const char *name = "anonymous",
					 exposure *exp = NULL) {

		double resident = image::get_resident();

		if (resident == 0 || resident * 1000000 >= dimy * dimx)
			return new image_ale_real<0>(dimy, dimx, depth, name, exp);

		return new image_ale_real<1>(dimy, dimx, depth, name, exp);

	}

	image_ale_real (unsigned int dimy, unsigned int dimx, unsigned int
			depth, const char *name = "anonymous", exposure *exp = NULL) 
			: image(dimy, dimx, depth, name, exp) {

		if (disk_support == 0) {
			_p = new spixel[dimx * dimy];

			assert (_p);

			if (!_p) {
				fprintf(stderr, "Could not allocate memory for image data.\n");
				exit(1);
			}
		} else {
			rows_per_segment = (int) ceil((double) dimy / (double) RESIDENT_DIVISIONS);

			assert (rows_per_segment > 0);

			for (int i = 0; i < RESIDENT_DIVISIONS; i++) {
				_p_segments[i] = NULL;
				dirty_segments[i] = 0;
				resident_list[i] = -1;
			}

			resident_max = (unsigned int) floor((image::get_resident() * 1000000)
				                          / (rows_per_segment * dimx));

			assert (resident_max <= RESIDENT_DIVISIONS);

			if (resident_max == 0) {
				ui::get()->error_hint(
					"No segments resident in image array.",
					"Try recompiling with more RESIDENT_DIVISIONS");
			}

			resident_next = 0;

			support = tmpfile();

			if (!support) {
				ui::get()->error_hint(
					"Unable to create temporary file to support image array.",
					"Set --resident 0, or Win32/64 users might run as admin.");
			}

			spixel *zero = new spixel[dimx];

			assert(zero);

			for (unsigned int i = 0; i < dimy; i++) {
				unsigned int c = fwrite(zero, sizeof(spixel), dimx, support);
				if (c < dimx)
					ui::get()->error_hint("Image array support file error.",
					                      "Submit a bug report.");
			}

			delete[] zero;
		}
	}

	virtual ~image_ale_real() {
		if (disk_support == 0) {
			delete[] _p;
		} else {
			for (int i = 0; i < RESIDENT_DIVISIONS; i++) {
				if (_p_segments[i])
					delete[] _p_segments[i];
			}

			fclose(support);
		}
	}

	void resident_begin(unsigned int segment) const {
		rwlock.rdlock();
		if (_p_segments[segment])
			return;
		rwlock.unlock();

		rwlock.wrlock();

		if (_p_segments[segment])
			return;

		if (resident_list[resident_next] >= 0) {
			/*
			 * Eject a segment
			 */

			if (dirty_segments[resident_list[resident_next]]) {
				fseek(support, rows_per_segment * _dimx * sizeof(spixel) 
				             * resident_list[resident_next], 
					       SEEK_SET);
				assert(_p_segments[resident_list[resident_next]]);
				fwrite(_p_segments[resident_list[resident_next]], 
					sizeof(spixel), rows_per_segment * _dimx, support);
				dirty_segments[resident_list[resident_next]] = 0;
			}

			delete[] _p_segments[resident_list[resident_next]];
			_p_segments[resident_list[resident_next]] = NULL;
		}

		resident_list[resident_next] = segment;

		_p_segments[segment] = new spixel[_dimx * rows_per_segment];

		assert (_p_segments[segment]);

		fseek(support, rows_per_segment * _dimx * sizeof(spixel)
		             * segment,
			     SEEK_SET);

		fread(_p_segments[segment], sizeof(spixel), rows_per_segment * _dimx, support);

		/*
		 * Update the next ejection candidate.
		 */
		resident_next++;
		if (resident_next >= resident_max)
			resident_next = 0;
	}

	void resident_end(unsigned int segment) const {
		rwlock.unlock();
	}

	spixel get_pixel(unsigned int y, unsigned int x) const {
		assert (x < _dimx);
		assert (y < _dimy);

		if (disk_support == 0) {
			return _p[y * _dimx + x];
		} else {
			int segment = y / rows_per_segment;
			assert (segment < RESIDENT_DIVISIONS);

			resident_begin(segment);

			spixel result = _p_segments[segment][(y % rows_per_segment) * _dimx + x];
				
			resident_end(segment);

			return result;
		}
	}

	void set_pixel(unsigned int y, unsigned int x, spixel p) {
		assert (x < _dimx);
		assert (y < _dimy);

		image_updated();

		if (disk_support == 0) {
			_p[y * _dimx + x] = p;
		} else {
			int segment = y / rows_per_segment;
			assert (segment < RESIDENT_DIVISIONS);

			resident_begin(segment);
				
			_p_segments[segment][(y % rows_per_segment) * _dimx + x] = p;
			dirty_segments[segment] = 1;
				
			resident_end(segment);
		}
	}

	void mul_pixel(unsigned int y, unsigned int x, spixel p) {
		assert (x < _dimx);
		assert (y < _dimy);

		image_updated();

		if (disk_support == 0) {
			_p[y * _dimx + x] *= p;
		} else {
			int segment = y / rows_per_segment;
			assert (segment < RESIDENT_DIVISIONS);

			resident_begin(segment);
				
			_p_segments[segment][(y % rows_per_segment) * _dimx + x] *= p;
			dirty_segments[segment] = 1;
				
			resident_end(segment);
		}
	}

	void add_pixel(unsigned int y, unsigned int x, pixel p) {
		assert (x < _dimx);
		assert (y < _dimy);

		image_updated();

		if (disk_support == 0) {
			_p[y * _dimx + x] += p;
		} else {
			int segment = y / rows_per_segment;
			assert (segment < RESIDENT_DIVISIONS);

			resident_begin(segment);
				
			_p_segments[segment][(y % rows_per_segment) * _dimx + x] += p;
			dirty_segments[segment] = 1;
				
			resident_end(segment);
		}
	}

	ale_sreal get_chan(unsigned int y, unsigned int x, unsigned int k) const {
		assert (x < _dimx);
		assert (y < _dimy);

		if (disk_support == 0) {
			return _p[y * _dimx + x][k];
		} else {
			int segment = y / rows_per_segment;
			assert (segment < RESIDENT_DIVISIONS);

			resident_begin(segment);

			ale_sreal result = _p_segments[segment]
			                              [(y % rows_per_segment) * _dimx + x][k];
				
			resident_end(segment);

			return result;
		}
	}

	void set_chan(unsigned int y, unsigned int x, unsigned int k, ale_sreal c) {
		assert (x < _dimx);
		assert (y < _dimy);

		image_updated();

		if (disk_support == 0) {
			_p[y * _dimx + x][k] = c;
		} else {
			int segment = y / rows_per_segment;
			assert (segment < RESIDENT_DIVISIONS);

			resident_begin(segment);
				
			_p_segments[segment][(y % rows_per_segment) * _dimx + x][k] = c;
			dirty_segments[segment] = 1;
				
			resident_end(segment);
		}
	}

	void div_chan(unsigned int y, unsigned int x, unsigned int k, ale_sreal c) {
		assert (x < _dimx);
		assert (y < _dimy);

		image_updated();

		if (disk_support == 0) {
			_p[y * _dimx + x][k] /= c;
		} else {
			int segment = y / rows_per_segment;
			assert (segment < RESIDENT_DIVISIONS);

			resident_begin(segment);
				
			_p_segments[segment][(y % rows_per_segment) * _dimx + x][k] /= c;
			dirty_segments[segment] = 1;
				
			resident_end(segment);
		}
	}

	/*
	 * Make a new image suitable for receiving scaled values.
	 */
	virtual image *scale_generator(int height, int width, int depth, const char *name) const {
		return new_image_ale_real(height, width, depth, name, _exp);
	}

	/*
	 * Extend the image area to the top, bottom, left, and right,
	 * initializing the new image areas with black pixels.  Negative values
	 * shrink the image.
	 */
	image *_extend(int top, int bottom, int left, int right) {

		image *is = new_image_ale_real (
			height() + top  + bottom,
			 width() + left + right , depth(), name, _exp);

		assert(is);

		unsigned int min_i = (-top > 0)
			      ? -top
			      : 0;

		unsigned int min_j = (-left > 0)
			      ? -left
			      : 0;

		unsigned int max_i = (height() < is->height() - top)
			      ? height()
			      : is->height() - top;

		unsigned int max_j = (width() < is->width() - left)
			      ? width()
			      : is->width() - left;

		for (unsigned int i = min_i; i < max_i; i++)
		for (unsigned int j = min_j; j < max_j; j++) 
			is->set_pixel(i + top, j + left, get_pixel(i, j));

		is->set_offset(_offset[0] - top, _offset[1] - left);

		return is;
	}

private:
	void trigger(pixel multiplier) {
		for (unsigned int i = 0; i < height(); i++)
		for (unsigned int j = 0; j < width(); j++) {
			mul_pixel(i, j, multiplier);
		}
	}
};

/*
 * Wrapper encapsulating details of the separation between the
 * resident-checking implementation and non-checking.
 */
static inline image *new_image_ale_real(unsigned int dimy,
                                 unsigned int dimx,
				 unsigned int depth,
				 const char *name = "anonymous",
				 exposure *exp = NULL) {

	return image_ale_real<0>::new_image_ale_real(dimy, dimx, depth, name, exp);
}


#endif
