/* Manage sets of mmap buffers on an image.
 * 
 * 30/10/06
 *	- from region.c
 * 19/3/09
 *	- block mmaps of nodata images
 */

/*

    This file is part of VIPS.
    
    VIPS is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program 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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 */

/*

    These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk

 */

/*
#define DEBUG_TOTAL
#define DEBUG
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>

#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif /*HAVE_UNISTD_H*/
#include <errno.h>
#include <string.h>
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif
#include <assert.h>

#include <vips/vips.h>
#include <vips/internal.h>
#include <vips/thread.h>

#ifdef OS_WIN32
#include <windows.h>
#endif /*OS_WIN32*/

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif /*WITH_DMALLOC*/

/* Sanity checking ... write to this during read tests to make sure we don't
 * get optimised out.
 */
int im__read_test;

/* Add this many lines above and below the mmap() window.
 */
int im__window_margin_pixels = IM__WINDOW_MARGIN_PIXELS;

/* Always map at least this many bytes. There's no point making tiny windows
 * on small files.
 */
int im__window_margin_bytes = IM__WINDOW_MARGIN_BYTES;

/* Track global mmap usage.
 */
#ifdef DEBUG_TOTAL
static int total_mmap_usage = 0;
static int max_mmap_usage = 0;
#endif /*DEBUG_TOTAL*/

static int
im_window_unmap( im_window_t *window )
{
	/* unmap the old window
	 */
	if( window->baseaddr ) {
		if( im__munmap( window->baseaddr, window->length ) )
			return( -1 );

#ifdef DEBUG_TOTAL
		g_mutex_lock( im__global_lock );
		total_mmap_usage -= window->length;
		assert( total_mmap_usage >= 0 );
		g_mutex_unlock( im__global_lock );
#endif /*DEBUG_TOTAL*/

		window->data = NULL;
		window->baseaddr = NULL;
		window->length = 0;
	}

	return( 0 );
}

static int
im_window_free( im_window_t *window )
{
	assert( window->ref_count == 0 );

#ifdef DEBUG
	printf( "** im_window_free: window top = %d, height = %d (%p)\n",
		window->top, window->height, window );
#endif /*DEBUG*/

	if( im_window_unmap( window ) )
		return( -1 );

	window->im = NULL;

	im_free( window );

	return( 0 );
}

int
im_window_unref( im_window_t *window )
{
	IMAGE *im = window->im;

	g_mutex_lock( im->sslock );

#ifdef DEBUG
	printf( "im_window_unref: window top = %d, height = %d, count = %d\n",
		window->top, window->height, window->ref_count );
#endif /*DEBUG*/

	assert( window->ref_count > 0 );

	window->ref_count -= 1;

	if( window->ref_count == 0 ) {
		assert( g_slist_find( im->windows, window ) );
		im->windows = g_slist_remove( im->windows, window );

#ifdef DEBUG
		printf( "im_window_unref: %d windows left\n",
			g_slist_length( im->windows ) );
#endif /*DEBUG*/

		if( im_window_free( window ) ) {
			g_mutex_unlock( im->sslock );
			return( -1 );
		}
	}

	g_mutex_unlock( im->sslock );

	return( 0 );
}

#ifdef DEBUG_TOTAL
static void
trace_mmap_usage( void )
{
	g_mutex_lock( im__global_lock );
	{
		static int last_total = 0;
		int total = total_mmap_usage / (1024 * 1024);
		int max = max_mmap_usage / (1024 * 1024);

		if( total != last_total ) {
			printf( "im_window_set: current mmap "
				"usage of ~%dMB (high water mark %dMB)\n", 
				total, max );
			last_total = total;
		}
	}
	g_mutex_unlock( im__global_lock );
}
#endif /*DEBUG_TOTAL*/

static int
im_getpagesize()
{
	static int pagesize = 0;

	if( !pagesize ) {
#ifdef OS_WIN32
		SYSTEM_INFO si;

		GetSystemInfo( &si );

		pagesize = si.dwAllocationGranularity;
#else /*OS_WIN32*/
		pagesize = getpagesize();
#endif /*OS_WIN32*/

#ifdef DEBUG_TOTAL
		printf( "im_getpagesize: 0x%x\n", pagesize );
#endif /*DEBUG_TOTAL*/
	}

	return( pagesize );
}

/* Map a window into a file.
 */
static int
im_window_set( im_window_t *window, int top, int height )
{
	int pagesize = im_getpagesize();

	void *baseaddr;
	gint64 start, end, pagestart;
	size_t length, pagelength;

	/* Calculate start and length for our window. 
	 */
	start = window->im->sizeof_header + 
		(gint64) IM_IMAGE_SIZEOF_LINE( window->im ) * top;
	length = (size_t) IM_IMAGE_SIZEOF_LINE( window->im ) * height;

	pagestart = start - start % pagesize;
	end = start + length;
	pagelength = end - pagestart;

	/* Make sure we have enough file.
	 */
	if( end > window->im->file_length ) {
		im_error( "im_window_set", 
			_( "unable to read data for \"%s\", %s" ),
			window->im->filename, _( "file has been truncated" ) );
		return( -1 );
	}

	if( !(baseaddr = im__mmap( window->im->fd, 0, pagelength, pagestart )) )
		return( -1 ); 

	window->baseaddr = baseaddr;
	window->length = pagelength;

	window->data = (char *) baseaddr + (start - pagestart);
	window->top = top;
	window->height = height;

	/* Sanity check ... make sure the data pointer is readable.
	 */
	im__read_test &= window->data[0];

#ifdef DEBUG_TOTAL
	g_mutex_lock( im__global_lock );
	total_mmap_usage += window->length;
	if( total_mmap_usage > max_mmap_usage )
		max_mmap_usage = total_mmap_usage;
	g_mutex_unlock( im__global_lock );
	trace_mmap_usage();
#endif /*DEBUG_TOTAL*/

	return( 0 );
}

/* Make a new window.
 */
static im_window_t *
im_window_new( IMAGE *im, int top, int height )
{
	im_window_t *window;

	if( !(window = IM_NEW( NULL, im_window_t )) )
		return( NULL );

	window->ref_count = 0;
	window->im = im;
	window->top = 0;
	window->height = 0;
	window->data = NULL;
	window->baseaddr = NULL;
	window->length = 0;

	if( im_window_set( window, top, height ) ) {
		im_window_free( window );
		return( NULL );
	}

	im->windows = g_slist_prepend( im->windows, window );
	window->ref_count += 1;

#ifdef DEBUG
	printf( "** im_window_new: window top = %d, height = %d (%p)\n",
		window->top, window->height, window );
#endif /*DEBUG*/

	return( window );
}

/* A request for an area of pixels.
 */
typedef struct {
	int top;
	int height;
} request_t;

static void *
im_window_fits( im_window_t *window, request_t *req )
{
	if( window->top <= req->top && 
		window->top + window->height >= req->top + req->height )
		return( window );

	return( NULL );
}

/* Find an existing window that fits within top/height and return a ref.
 */
static im_window_t *
im_window_find( IMAGE *im, int top, int height )
{
	request_t req;
	im_window_t *window;

	req.top = top;
	req.height = height;
	window = im_slist_map2( im->windows, 
		(VSListMap2Fn) im_window_fits, &req, NULL );

	if( window ) {
		window->ref_count += 1;

#ifdef DEBUG
		printf( "im_window_find: ref window top = %d, height = %d, "
			"count = %d\n",
			top, height, window->ref_count );
#endif /*DEBUG*/
	}

	return( window );
}

/* Return a ref to a window that encloses top/height.
 */
im_window_t *
im_window_ref( IMAGE *im, int top, int height )
{
	im_window_t *window;

	g_mutex_lock( im->sslock );

	if( !(window = im_window_find( im, top, height )) ) {
		/* No existing window ... make a new one. Ask for a larger
		 * window than we strictly need. There's no point making tiny
		 * windows.
		 */
		int margin = IM_MIN( im__window_margin_pixels,
			im__window_margin_bytes / IM_IMAGE_SIZEOF_LINE( im ) );

		top -= margin;
		height += margin * 2;

		top = IM_CLIP( 0, top, im->Ysize - 1 );
		height = IM_CLIP( 0, height, im->Ysize - top );

		if( !(window = im_window_new( im, top, height )) ) {
			g_mutex_unlock( im->sslock );
			return( NULL );
		}
	}

	g_mutex_unlock( im->sslock );

	return( window );
}

void
im_window_print( im_window_t *window )
{
	printf( "im_window_t: %p ref_count = %d, ", window, window->ref_count );
	printf( "im = %p, ", window->im );
	printf( "top = %d, ", window->top );
	printf( "height = %d, ", window->height );
	printf( "data = %p, ", window->data );
	printf( "baseaddr = %p, ", window->baseaddr );
	printf( "length = %zd\n", window->length );
}
