/* doc.c
 * This file is part of Amsam photo cropping program
 * Copyright 2021, 2023, 2024, 2025, 2026 Nandakumar Edamana
 * Distributed under GNU General Public License, version 3
 */ 

#include <assert.h>
#include <stdarg.h>
#include <stdbool.h>

#include "doc.h"

void intvector__resize(intvector *this, int newcount)
{
	if (newcount > this->alloccount) {
		int alloccountbak = this->alloccount;

		this->alloccount = 2 * this->alloccount;

		assert(this->alloccount > alloccountbak);

		assert(newcount <= this->alloccount);
		this->arr = (int *) realloc(this->arr, this->alloccount * sizeof(this->arr[1u]));
		if (this->arr == NULL) {
			perror(NULL);
			exit(EXIT_FAILURE);
		}
	}

	this->count = newcount;
}

void intvector_append(intvector *this, int newitem)
{
	int newcount = 1 + this->count;

	intvector__resize(this, newcount);

	this->arr[newcount - 1] = newitem;
}

void intvector_clear(intvector *this)
{
	this->count = 0;
	this->alloccount = 8;
	this->arr = (int *) realloc(this->arr, this->alloccount * sizeof(this->arr[1u]));
	if (this->arr == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}
}

int intvector_get_item(intvector *this, int index)
{
	assert(index < this->count);

	return this->arr[index];
}

int intvector_pop(intvector *this)
{
	assert(this->count > 0);
	int r = intvector_get_item(this, this->count - 1);
	this->count = this->count - 1;

	return r;
}

void intvector_set_item(intvector *this, int index, int itm)
{
	assert(index < this->count);

	this->arr[index] = itm;
}

int intvector_get_count(intvector *this)
{
	return this->count;
}

_Bool intvector_is_empty(intvector *this)
{
	return 0 == this->count;
}

void intvector_destruct(intvector *this)
{
	if (this->arr) {
		free(this->arr);
	}
}

void intvector_construct(intvector *this)
{
	this->alloccount = 8;
	int *_tmp_1 = (int *) calloc((size_t) 8, sizeof(int));
	if (_tmp_1 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	this->arr = _tmp_1;
	this->count = 0;
}

Pixel PIXEL_CHECKER_DARK = (Pixel) { { 0.5, 0.5, 0.5, 1 } };
Pixel PIXEL_CHECKER_LIGHT = (Pixel) { { 0.8, 0.8, 0.8, 1 } };
Pixel PIXEL_SCRATCH_DARK = (Pixel) { { 0.1, 0.1, 0.1, 1 } };
Pixel PIXEL_SCRATCH_LIGHT = (Pixel) { { 0.8, 0.8, 0, 1 } };

void layer_owning_vector_destruct(LayerOwningVector *this)
{
	layer_owning_vector_delete_all(this);
	if (this->arr) {
		free(this->arr);
	}
}

void layer_owning_vector__resize(LayerOwningVector *this, int newcount)
{
	if (newcount > this->alloccount) {
		int alloccountbak = this->alloccount;

		this->alloccount = 2 * this->alloccount;

		assert(this->alloccount > alloccountbak);

		assert(newcount <= this->alloccount);
		this->arr = (Layer * *) realloc(this->arr, this->alloccount * sizeof(this->arr[1u]));
		if (this->arr == NULL) {
			perror(NULL);
			exit(EXIT_FAILURE);
		}
	}

	this->count = newcount;
}

void layer_owning_vector_append(LayerOwningVector *this, Layer *newitem)
{
	int newcount = 1 + this->count;

	layer_owning_vector__resize(this, newcount);

	this->arr[newcount - 1] = newitem;
}

void layer_owning_vector_clear(LayerOwningVector *this)
{
	layer_owning_vector_delete_all(this);

	this->count = 0;
	this->arr = (Layer * *) realloc(this->arr, 8 * sizeof(this->arr[1u]));
	if (this->arr == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	this->alloccount = 8;
}

void layer_owning_vector_delete_all(LayerOwningVector *this)
{
	while (this->count > 0) {
		Layer *obj = layer_owning_vector_pop(this);
		if (obj) {
			layer_destruct(obj);
			free(obj);
		}
	}
}

Layer * layer_owning_vector_get_item(LayerOwningVector *this, int index)
{
	assert(index < this->count);

	return this->arr[index];
}

Layer * layer_owning_vector_pop(LayerOwningVector *this)
{
	assert(this->count > 0);
	Layer *r = layer_owning_vector_get_item(this, this->count - 1);
	this->count = this->count - 1;

	return r;
}

void layer_owning_vector_set_item(LayerOwningVector *this, int index, Layer *itm)
{
	assert(index < this->count);

	Layer *todel = this->arr[index];

	this->arr[index] = itm;
	if (todel) {
		layer_destruct(todel);
		free(todel);
	}
}

int layer_owning_vector_get_count(LayerOwningVector *this)
{
	return this->count;
}

_Bool layer_owning_vector_is_empty(LayerOwningVector *this)
{
	return 0 == this->count;
}

void layer_owning_vector_construct(LayerOwningVector *this)
{
	this->alloccount = 8;
	Layer * *_tmp_1 = (Layer * *) calloc((size_t) 8, sizeof(Layer *));
	if (_tmp_1 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	this->arr = _tmp_1;
	this->count = 0;
}

int _ngg_clsid_HistoryPoint = 0;
void history_point__nggnonvirt_destruct(HistoryPoint *this)
{
}

void history_point_destruct(HistoryPoint *this)
{
	this->_ngg_vtab_history_point.destruct(this);
}

void history_point_construct(HistoryPoint *this)
{
	this->_ngg_clsid_final = &_ngg_clsid_HistoryPoint;
	this->_ngg_vtab_history_point.destruct = (void (*)(HistoryPoint *this)) history_point__nggnonvirt_destruct;
}

int _ngg_clsid_ImageHistoryPoint = 0;
void image_history_point_construct(ImageHistoryPoint *this, int layeridx, Image *img)
{
	history_point_construct((HistoryPoint *) this);
	this->_ngg_clsid_final = &_ngg_clsid_ImageHistoryPoint;
	this->_ngg_vtab_history_point.destruct = (void (*)(HistoryPoint *this)) image_history_point__nggnonvirt_destruct;
	this->img = image_Clone(img);
	this->layeridx = layeridx;
}

Image * image_history_point_detach_img(ImageHistoryPoint *this)
{
	Image *_tmp_1;
	_tmp_1 = this->img;
	this->img = NULL;
	assert(_tmp_1);
	return _tmp_1;
}

void image_history_point__nggnonvirt_destruct(ImageHistoryPoint *this)
{
	if (this->img) {
		image_destruct((Image *) (this->img));
		free(this->img);
	}

	history_point__nggnonvirt_destruct((HistoryPoint *) this);
}

int _ngg_clsid_LayerStackHistoryPoint = 0;
void layer_stack_history_point_construct(LayerStackHistoryPoint *this, intvector *layerstack, int curlayeridxidx)
{
	history_point_construct((HistoryPoint *) this);
	this->_ngg_clsid_final = &_ngg_clsid_LayerStackHistoryPoint;
	this->_ngg_vtab_history_point.destruct = (void (*)(HistoryPoint *this)) layer_stack_history_point__nggnonvirt_destruct;
	intvector *_tmp_1 = (intvector *) malloc(sizeof(intvector));
	if (_tmp_1 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	intvector_construct(_tmp_1);
	this->layerstack = _tmp_1;

	this->curlayeridxidx = curlayeridxidx;

	for (int _ngg_tmp_1 = 0; _ngg_tmp_1 < intvector_get_count(layerstack); _ngg_tmp_1 += 1) {
		int lidx;
		lidx = intvector_get_item(layerstack, _ngg_tmp_1);
		assert(this->layerstack);
		intvector_append(this->layerstack, lidx);
	}
}

intvector * layer_stack_history_point_detach_layerstack(LayerStackHistoryPoint *this)
{
	intvector *_tmp_1;
	_tmp_1 = this->layerstack;
	this->layerstack = NULL;
	assert(_tmp_1);
	return _tmp_1;
}

void layer_stack_history_point__nggnonvirt_destruct(LayerStackHistoryPoint *this)
{
	if (this->layerstack) {
		intvector_destruct((intvector *) (this->layerstack));
		free(this->layerstack);
	}

	history_point__nggnonvirt_destruct((HistoryPoint *) this);
}

int history_point_stack_get_count(HistoryPointStack *this)
{
	return this->count;
}

_Bool history_point_stack_is_empty(HistoryPointStack *this)
{
	return 0 == this->count;
}

void history_point_stack_push(HistoryPointStack *this, HistoryPoint *newitem)
{
	history_point_stack__resize(this, this->count + 1);
	this->arr[this->count - 1] = newitem;
}

void history_point_stack_push_multiple(HistoryPointStack *this, int n, HistoryPoint * *newitems)
{
	int newcount = this->count + n;
	if (this->alloccount < newcount) {
		this->arr = (HistoryPoint * *) realloc(this->arr, newcount * sizeof(this->arr[1u]));
		if (this->arr == NULL) {
			perror(NULL);
			exit(EXIT_FAILURE);
		}

		this->alloccount = newcount;
	}

	for (int i = 0; i < n; i += 1) {
		this->arr[this->count + i] = newitems[i];
	}

	this->count = newcount;
}

HistoryPoint * history_point_stack_peek(HistoryPointStack *this)
{
	assert(this->count > 0);
	return this->arr[this->count - 1];
}

HistoryPoint * history_point_stack_pop(HistoryPointStack *this)
{
	assert(this->count > 0);

	HistoryPoint *retitem = this->arr[this->count - 1];
	history_point_stack__resize(this, this->count - 1);

	return retitem;
}

HistoryPoint * history_point_stack_pop_bottom(HistoryPointStack *this)
{
	assert(this->count > 0);

	HistoryPoint *retval = this->arr[0];

	this->count -= 1;
	memmove(this->arr, this->arr + 1, this->count * sizeof(this->arr[0]));

	return retval;
}

void history_point_stack_replace_top(HistoryPointStack *this, HistoryPoint *newitem)
{
	assert(this->count > 0);

	this->arr[this->count - 1] = newitem;
}

void history_point_stack__resize(HistoryPointStack *this, int newcount)
{
	if (newcount > this->alloccount) {
		int alloccountbak = this->alloccount;

		this->alloccount = 2 * this->alloccount;

		assert(this->alloccount > alloccountbak);

		assert(newcount <= this->alloccount);
		this->arr = (HistoryPoint * *) realloc(this->arr, this->alloccount * sizeof(this->arr[1u]));
		if (this->arr == NULL) {
			perror(NULL);
			exit(EXIT_FAILURE);
		}
	}

	this->count = newcount;
}

void history_point_stack_destruct(HistoryPointStack *this)
{
	if (this->arr) {
		free(this->arr);
	}
}

void history_point_stack_construct(HistoryPointStack *this)
{
	this->alloccount = 8;
	HistoryPoint * *_tmp_1 = (HistoryPoint * *) calloc((size_t) 8, sizeof(HistoryPoint *));
	if (_tmp_1 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	this->arr = _tmp_1;
	this->count = 0;
}

void image_document_construct(ImageDocument *this, int width, int height, DrawingParams drawparams, _Bool create_init_layers)
{
	HistoryPointStack *_tmp_1 = (HistoryPointStack *) malloc(sizeof(HistoryPointStack));
	if (_tmp_1 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	history_point_stack_construct(_tmp_1);
	this->redostack = _tmp_1;
	HistoryPointStack *_tmp_2 = (HistoryPointStack *) malloc(sizeof(HistoryPointStack));
	if (_tmp_2 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	history_point_stack_construct(_tmp_2);
	this->undostack = _tmp_2;
	intvector *_tmp_3 = (intvector *) malloc(sizeof(intvector));
	if (_tmp_3 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	intvector_construct(_tmp_3);
	this->layerstack = _tmp_3;
	LayerOwningVector *_tmp_4 = (LayerOwningVector *) malloc(sizeof(LayerOwningVector));
	if (_tmp_4 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	layer_owning_vector_construct(_tmp_4);
	this->layers = _tmp_4;
	this->history_changed_userdata = NULL;
	this->history_changed_cbk = NULL;
	this->prescratchbak = NULL;
	this->autocenter = false;
	this->oldpreviewzoom = 0.0;
	this->oldpreviewh = 0;
	this->oldprevieww = 0;
	this->oldpreviewy = 0;
	this->oldpreviewx = 0;
	this->vpy = 0;
	this->vpx = 0;
	this->scratchmode = false;
	this->oldy = 0.0;
	this->oldx = 0.0;
	this->pen_is_down = false;
	this->preview_is_all_dirty = false;
	this->scratchborder = NULL;
	this->preview = NULL;
	this->dirtyrect = vara_rect_default();
	this->layerstack_version = 0;
	this->curlayeridxidx = 0;
	assert(width > 0);

	assert(height > 0);
	this->width = width;
	this->height = height;

	this->bgcolor = LINEAR_PIXEL_WHITE;

	Image *_tmp_5 = (Image *) malloc(sizeof(Image));
	if (_tmp_5 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	image_construct(_tmp_5, width, height, 255);
	this->checkpattern = _tmp_5;
	image_fill_checkpattern(this->checkpattern);

	this->drawparams = drawparams;
	this->stroke_smoothness = 0.1;

	if (create_init_layers) {
		image_document_create_layer(this, "Background", LINEAR_PIXEL_WHITE);
		image_document_create_layer_and_switch(this, "Foreground", LINEAR_PIXEL_TRANSP);
	}

	this->subpixel_when_zoomed = true;

	this->vpzoom = 1;
	Image *_tmp_6 = (Image *) malloc(sizeof(Image));
	if (_tmp_6 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	image_construct(_tmp_6, width, height, 255);
	this->composite = _tmp_6;

	this->in_bg_fg_mode = create_init_layers;

	image_document_mark_all_dirty(this);
}

void image_document_destruct(ImageDocument *this)
{
	image_document_clear_undostack(this);
	image_document_clear_redostack(this);
	if (this->checkpattern) {
		image_destruct(this->checkpattern);
		free(this->checkpattern);
	}

	if (this->layers) {
		layer_owning_vector_destruct(this->layers);
		free(this->layers);
	}

	if (this->layerstack) {
		intvector_destruct(this->layerstack);
		free(this->layerstack);
	}

	if (this->composite) {
		image_destruct(this->composite);
		free(this->composite);
	}

	if (this->preview) {
		image_destruct((Image *) (this->preview));
		free(this->preview);
	}

	if (this->scratchborder) {
		image_destruct((Image *) (this->scratchborder));
		free(this->scratchborder);
	}

	if (this->undostack) {
		history_point_stack_destruct(this->undostack);
		free(this->undostack);
	}

	if (this->redostack) {
		history_point_stack_destruct(this->redostack);
		free(this->redostack);
	}

	if (this->prescratchbak) {
		((HistoryPoint *) (this->prescratchbak))->_ngg_vtab_history_point.destruct((HistoryPoint *) (this->prescratchbak));
		free(this->prescratchbak);
	}
}

void image_document_apply_snapshot(ImageDocument *this, HistoryPoint *hp)
{
	if (hp->_ngg_clsid_final == &_ngg_clsid_ImageHistoryPoint) {
		ImageHistoryPoint *ihp = (ImageHistoryPoint *) hp;
		layer_replace_image(layer_owning_vector_get_item(this->layers, ihp->layeridx), image_history_point_detach_img(ihp));
	} else if (hp->_ngg_clsid_final == &_ngg_clsid_LayerStackHistoryPoint) {
		LayerStackHistoryPoint *lshp = (LayerStackHistoryPoint *) hp;
		image_document_replace_layerstack(this, layer_stack_history_point_detach_layerstack(lshp));
		this->curlayeridxidx = lshp->curlayeridxidx;
	}

	image_document_mark_all_dirty(this);
}

void image_document_append_layer(ImageDocument *this, Layer *layer)
{
	layer_owning_vector_append(this->layers, layer);
	intvector_append(this->layerstack, layer_owning_vector_get_count(this->layers) - 1);
	this->layerstack_version += 1;
	this->in_bg_fg_mode = false;
}

_Bool image_document_can_create_layer(ImageDocument *this)
{
	return !this->scratchmode;
}

_Bool image_document_can_delete_layer(ImageDocument *this)
{
	return !this->scratchmode && intvector_get_count(this->layerstack) > 1;
}

_Bool image_document_can_move_layer_down(ImageDocument *this)
{
	return !this->scratchmode && image_document_get_curlayeridxidx(this) > 0;
}

_Bool image_document_can_move_layer_up(ImageDocument *this)
{
	return !this->scratchmode && image_document_get_curlayeridxidx(this) < intvector_get_count(this->layerstack) - 1;
}

_Bool image_document_can_redo(ImageDocument *this)
{
	return !this->scratchmode && !history_point_stack_is_empty(this->redostack);
}

_Bool image_document_can_rename_layer(ImageDocument *this)
{
	return !this->scratchmode;
}

_Bool image_document_can_switch_layer(ImageDocument *this)
{
	return !this->scratchmode;
}

_Bool image_document_can_undo(ImageDocument *this)
{
	return !this->scratchmode && !history_point_stack_is_empty(this->undostack);
}

void image_document_clear_layer(ImageDocument *this)
{
	image_document_fill_layer(this, LINEAR_PIXEL_TRANSP);
}

void image_document_clear_redostack(ImageDocument *this)
{
	while (!history_point_stack_is_empty(this->redostack)) {
		HistoryPoint *todel = history_point_stack_pop(this->redostack);
		if (todel) {
			todel->_ngg_vtab_history_point.destruct(todel);
			free(todel);
		}
	}
}

void image_document_clear_undostack(ImageDocument *this)
{
	while (!history_point_stack_is_empty(this->undostack)) {
		HistoryPoint *todel = history_point_stack_pop(this->undostack);
		if (todel) {
			todel->_ngg_vtab_history_point.destruct(todel);
			free(todel);
		}
	}
}

void image_document_composite_layerstack(ImageDocument *this, Image *target, _Bool all_dirty, VaraRect dirtyrect)
{
	for (int _ngg_tmp_2 = 0; _ngg_tmp_2 < intvector_get_count(this->layerstack); _ngg_tmp_2 += 1) {
		int idx;
		idx = intvector_get_item(this->layerstack, _ngg_tmp_2);
		Layer *layer = layer_owning_vector_get_item(this->layers, idx);

		if (layer->visible) {
			if (all_dirty) {
				image_overlay_image_with_alpha(target, layer->img, layer->alpha);
			} else {
				image_overlay_image_region_with_alpha(target, layer->img, layer->alpha, dirtyrect);
			}
		}
	}
}

Image * image_document_create_export_composite(ImageDocument *this, _Bool fillbg)
{
	Image *ecomposite = (Image *) malloc(sizeof(Image));
	if (ecomposite == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	image_construct(ecomposite, this->composite->width, this->composite->height, 255);

	if (fillbg) {
		image_fill(ecomposite, this->bgcolor);
	}

	image_document_composite_layerstack(this, ecomposite, true, vara_rect_default());

	return ecomposite;
}

HistoryPoint * image_document_create_image_history_point(ImageDocument *this)
{
	ImageHistoryPoint *_tmp_1 = (ImageHistoryPoint *) malloc(sizeof(ImageHistoryPoint));
	if (_tmp_1 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	image_history_point_construct(_tmp_1, image_document_get_curlayeridx(this), image_document_get_curlayer(this)->img);
	return (HistoryPoint *) _tmp_1;
}

void image_document_create_layer(ImageDocument *this, const char *name, LinearPixel bgcolor)
{
	image_document_create_layerstack_undo_snapshot(this);
	Layer *_tmp_1 = (Layer *) malloc(sizeof(Layer));
	if (_tmp_1 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	layer_construct(_tmp_1, name, this->width, this->height, bgcolor);
	image_document_append_layer(this, _tmp_1);
}

void image_document_create_layer_and_switch(ImageDocument *this, const char *name, LinearPixel bgcolor)
{
	image_document_create_layer(this, name, bgcolor);
	this->curlayeridxidx = intvector_get_count(this->layerstack) - 1;
}

HistoryPoint * image_document_create_layerstack_history_point(ImageDocument *this)
{
	LayerStackHistoryPoint *_tmp_1 = (LayerStackHistoryPoint *) malloc(sizeof(LayerStackHistoryPoint));
	if (_tmp_1 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	layer_stack_history_point_construct(_tmp_1, this->layerstack, this->curlayeridxidx);
	return (HistoryPoint *) _tmp_1;
}

void image_document_create_undo_snapshot(ImageDocument *this, HistoryPoint *hp)
{
	if (this->scratchmode) {
		return;
	}

	image_document_clear_redostack(this);

	if (this->undostack->count > STACKLIMIT) {
		HistoryPoint *todel = history_point_stack_pop_bottom(this->undostack);

		assert(this->undostack->count == STACKLIMIT);
		if (todel) {
			todel->_ngg_vtab_history_point.destruct(todel);
			free(todel);
		}
	}

	history_point_stack_push(this->undostack, hp);

	if (this->history_changed_cbk) {
		void (*cbknn)(void *userdata);
		cbknn = (void (*)(void *userdata)) (this->history_changed_cbk);
		cbknn(this->history_changed_userdata);
	}
}

void image_document_create_image_undo_snapshot(ImageDocument *this)
{
	image_document_create_undo_snapshot(this, image_document_create_image_history_point(this));
}

void image_document_create_layerstack_undo_snapshot(ImageDocument *this)
{
	image_document_create_undo_snapshot(this, image_document_create_layerstack_history_point(this));
}

void image_document_delete_layer(ImageDocument *this)
{
	if (!image_document_can_delete_layer(this)) {
		vara_log_error("delete-layer issued when it is disabled");
		return;
	}

	image_document_create_layerstack_undo_snapshot(this);

	intvector_delete_item(this->layerstack, this->curlayeridxidx);
	this->layerstack_version += 1;
	this->in_bg_fg_mode = false;

	if (this->curlayeridxidx >= intvector_get_count(this->layerstack)) {
		this->curlayeridxidx = intvector_get_count(this->layerstack) - 1;
	}

	image_document_mark_all_dirty(this);
}

Layer * image_document_get_curlayer(ImageDocument *this)
{
	return layer_owning_vector_get_item(this->layers, image_document_get_curlayeridx(this));
}

int image_document_get_curlayeridx(ImageDocument *this)
{
	return intvector_get_item(this->layerstack, image_document_get_curlayeridxidx(this));
}

int image_document_get_curlayeridxidx(ImageDocument *this)
{
	assert(this->curlayeridxidx <= intvector_get_count(this->layerstack) - 1 && this->curlayeridxidx >= 0);

	return this->curlayeridxidx;
}

Layer * image_document_get_bglayer_must(ImageDocument *this)
{
	return image_document_get_layer_must(this, 0);
}

Layer * image_document_get_fglayer_must(ImageDocument *this)
{
	return image_document_get_layer_must(this, 1);
}

Layer * image_document_get_layer(ImageDocument *this, int idx)
{
	Layer *_tmp_1;
	if (idx <= layer_owning_vector_get_count(this->layers) && idx >= 0) {
		_tmp_1 = layer_owning_vector_get_item(this->layers, idx);
	} else {
		_tmp_1 = NULL;
	}

	return _tmp_1;
}

Layer * image_document_get_layer_must(ImageDocument *this, int layeridx)
{
	return layer_owning_vector_get_item(this->layers, layeridx);
}

_Bool image_document_has_bglayer(ImageDocument *this)
{
	return layer_owning_vector_get_count(this->layers) > 0;
}

_Bool image_document_has_fglayer(ImageDocument *this)
{
	return layer_owning_vector_get_count(this->layers) > 1;
}

void image_document_dip_eyedropper(ImageDocument *this, int x, int y)
{
	assert(this->drawparams.active_tool == TOOL_EYEDROPPER);

	this->drawparams.fgcolor = image_document_get_color_at(this, x, y);
}

VaraFileIOError image_document_export_document(ImageDocument *this, const char *filename, _Bool fillbg)
{
	Image *ecomposite = image_document_create_export_composite(this, fillbg);

	VaraFileIOError _tmp_1 = write_image_to_file(ecomposite, filename);
	if (ecomposite) {
		image_destruct(ecomposite);
		free(ecomposite);
	}

	return _tmp_1;
}

VaraFileIOError image_document_export_layer(ImageDocument *this, const char *filename, _Bool fillbg)
{
	Layer *curlayer = image_document_get_curlayer(this);

	Image *comp = (Image *) malloc(sizeof(Image));
	if (comp == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	image_construct(comp, curlayer->img->width, curlayer->img->height, curlayer->img->depth);

	if (fillbg) {
		image_fill(comp, this->bgcolor);
	}

	image_overlay_image_with_alpha(comp, curlayer->img, curlayer->alpha);

	VaraFileIOError _tmp_1 = write_image_to_file(comp, filename);
	if (comp) {
		image_destruct(comp);
		free(comp);
	}

	return _tmp_1;
}

void image_document_fill_layer(ImageDocument *this, LinearPixel color)
{
	image_document_create_image_undo_snapshot(this);

	image_fill(image_document_get_curlayer(this)->img, color);

	image_document_mark_all_dirty(this);
}

void image_document_fill_layer_with_bgcolor(ImageDocument *this)
{
	image_document_fill_layer(this, image_document_get_curlayer(this)->default_bgcolor);
}

void image_document_fill_layer_with_fgcolor(ImageDocument *this)
{
	image_document_fill_layer(this, this->drawparams.fgcolor);
}

void image_document_floodfill(ImageDocument *this, int x, int y)
{
	assert(this->drawparams.active_tool == TOOL_FLOODFILL);

	double tx = image_document_translate_x(this, x);
	double ty = image_document_translate_y(this, y);

	if (image_is_point_outside(image_document_get_curlayer(this)->img, (int) tx, (int) ty)) {
		return;
	}

	image_document_create_image_undo_snapshot(this);

	VaraRect rect = nan_img_floodfill(image_document_get_curlayer(this)->img, (int) tx, (int) ty, this->drawparams);
	image_document_add_dirty_region(this, rect);
}

LinearPixel image_document_get_color_at(ImageDocument *this, int x, int y)
{
	double tx = image_document_translate_x(this, x);
	double ty = image_document_translate_y(this, y);

	if (((tx >= 0 && tx < image_document_get_curlayer(this)->img->width) && ty >= 0) && ty < image_document_get_curlayer(this)->img->height) {
		LinearPixel px = nan_img_get_linear_pixel(image_document_get_curlayer(this)->img, (int) tx, (int) ty);

		px.rgb[3] = 1;
		return px;
	}

	return LINEAR_PIXEL_BLACK;
}

int image_document_get_height(ImageDocument *this)
{
	return this->height;
}

int image_document_get_width(ImageDocument *this)
{
	return this->width;
}

_Bool image_document_is_curlayer_bg(ImageDocument *this)
{
	return image_document_get_curlayeridxidx(this) == 0;
}

_Bool image_document_is_curlayer_fg(ImageDocument *this)
{
	return image_document_get_curlayeridxidx(this) == 1;
}

void image_document_add_dirty_region(ImageDocument *this, VaraRect rect)
{
	if (region_is_zero(this->dirtyrect)) {
		this->dirtyrect = rect;
		return;
	}

	this->dirtyrect = union_regions(this->dirtyrect, rect);
}

_Bool image_document_is_all_dirty(ImageDocument *this)
{
	return ((this->dirtyrect.x == 0 && this->dirtyrect.y == 0) && this->dirtyrect.w == this->composite->width) && this->dirtyrect.h == this->composite->height;
}

_Bool image_document_is_dirty(ImageDocument *this)
{
	return !region_is_zero(this->dirtyrect);
}

void image_document_mark_all_dirty(ImageDocument *this)
{
	this->dirtyrect.x = 0;
	this->dirtyrect.y = 0;
	this->dirtyrect.w = this->composite->width;
	this->dirtyrect.h = this->composite->height;
}

void image_document_mark_not_dirty(ImageDocument *this)
{
	this->dirtyrect.w = 0;
	this->dirtyrect.h = 0;
}

void image_document_move_layer_down(ImageDocument *this)
{
	if (!image_document_can_move_layer_down(this)) {
		vara_log_error("move-layer-down issued when it is disabled");
		return;
	}

	image_document_create_layerstack_undo_snapshot(this);

	intvector_swap_items(this->layerstack, this->curlayeridxidx, this->curlayeridxidx - 1);
	this->curlayeridxidx -= 1;
	this->layerstack_version += 1;

	image_document_mark_all_dirty(this);
}

void image_document_move_layer_up(ImageDocument *this)
{
	if (!image_document_can_move_layer_up(this)) {
		vara_log_error("move-layer-up issued when it is disabled");
		return;
	}

	image_document_create_layerstack_undo_snapshot(this);

	intvector_swap_items(this->layerstack, this->curlayeridxidx, this->curlayeridxidx + 1);
	this->curlayeridxidx += 1;
	this->layerstack_version += 1;

	image_document_mark_all_dirty(this);
}

void image_document_pen_down(ImageDocument *this, int x, int y, double pressure)
{
	image_document_create_image_undo_snapshot(this);

	this->pen_is_down = true;

	this->oldx = x;

	this->oldy = y;

	image_document_pen_stroke(this, x, y, pressure);
}

void image_document_pen_up(ImageDocument *this, int x, int y, double pressure)
{
	image_document_pen_stroke(this, x, y, pressure);
	this->pen_is_down = false;
}

void image_document_pen_stroke(ImageDocument *this, int ix, int iy, double pressure)
{
	image_document_update_pressure(this, pressure);

	double x = ix;
	double y = iy;

	if (this->stroke_smoothness > 0) {
		double dist = this->stroke_smoothness * 20;

		x = this->oldx + (x - this->oldx) / dist;
		y = this->oldy + (y - this->oldy) / dist;
	}

	double tx1 = image_document_translate_x(this, this->oldx);
	double ty1 = image_document_translate_y(this, this->oldy);
	double tx2 = image_document_translate_x(this, x);
	double ty2 = image_document_translate_y(this, y);

	if (!this->subpixel_when_zoomed && this->stroke_smoothness <= 0) {
		tx1 = (int) tx1;
		ty1 = (int) ty1;
		tx2 = (int) tx2;
		ty2 = (int) ty2;
	}

	VaraRect rect = image_line(image_document_get_curlayer(this)->img, tx1, ty1, tx2, ty2, this->drawparams);

	image_document_add_dirty_region(this, rect);

	this->oldx = x;
	this->oldy = y;
}

void image_document_rename_layer(ImageDocument *this, const char *newnam)
{
	if (!image_document_can_rename_layer(this)) {
		vara_log_error("rename-layer issued when it is disabled");
		return;
	}

	layer_rename(image_document_get_curlayer(this), newnam);
	this->layerstack_version += 1;
}

void image_document_replace_layerstack(ImageDocument *this, intvector *newls)
{
	intvector *_ngg_tmp_3 = newls;
	if (this->layerstack) {
		intvector_destruct(this->layerstack);
		free(this->layerstack);
	}

	this->layerstack = _ngg_tmp_3;
	this->layerstack_version += 1;
}

void image_document_switch_layer(ImageDocument *this, int stackidx)
{
	if (!image_document_can_switch_layer(this)) {
		vara_log_error("attempt to switch layer when it is disabled");
		return;
	}

	assert(stackidx <= intvector_get_count(this->layerstack) - 1 && stackidx >= 0);
	this->curlayeridxidx = stackidx;
}

void image_document_switch_layer_bg(ImageDocument *this)
{
	image_document_switch_layer(this, 0);
}

void image_document_switch_layer_fg(ImageDocument *this)
{
	image_document_switch_layer(this, 1);
}

double image_document_translate_x(ImageDocument *this, double x)
{
	assert(this->vpzoom > 0);
	return (x - this->vpx) / this->vpzoom;
}

double image_document_translate_y(ImageDocument *this, double y)
{
	assert(this->vpzoom > 0);
	return (y - this->vpy) / this->vpzoom;
}

void image_document_update_pressure(ImageDocument *this, double pressure)
{
	this->drawparams.oldpressure = this->drawparams.pressure;
	this->drawparams.pressure = pressure;
}

void image_document_undo(ImageDocument *this)
{
	if (!image_document_can_undo(this)) {
		vara_log_error("undo issued when it is disabled");
		return;
	}

	HistoryPoint *hp = history_point_stack_pop(this->undostack);

	if (hp->_ngg_clsid_final == &_ngg_clsid_ImageHistoryPoint) {
		history_point_stack_push(this->redostack, image_document_create_image_history_point(this));
	} else if (hp->_ngg_clsid_final == &_ngg_clsid_LayerStackHistoryPoint) {
		history_point_stack_push(this->redostack, image_document_create_layerstack_history_point(this));
	}

	image_document_apply_snapshot(this, hp);

	if (this->history_changed_cbk) {
		void (*cbknn)(void *userdata);
		cbknn = (void (*)(void *userdata)) (this->history_changed_cbk);
		cbknn(this->history_changed_userdata);
	}
}

void image_document_viewport_align_center(ImageDocument *this)
{
	assert(this->preview);
	Image *preview = this->preview;

	this->vpx = (int) (preview->width / 2.0 - (this->vpzoom * this->composite->width) / 2.0);
	this->vpy = (int) (preview->height / 2.0 - (this->vpzoom * this->composite->height) / 2.0);
	this->autocenter = true;
}

void image_document_viewport_set_offsetx(ImageDocument *this, int x)
{
	this->vpx = x;
	this->autocenter = false;
}

void image_document_viewport_set_offsety(ImageDocument *this, int y)
{
	this->vpy = y;
	this->autocenter = false;
}

void image_document_viewport_set_size(ImageDocument *this, int w, int h)
{
	if (this->preview) {
		Image *previewnn;
		previewnn = (Image *) (this->preview);
		if (w == previewnn->width && h == previewnn->height) {
			return;
		}
	}

	Image *_tmp_1 = (Image *) malloc(sizeof(Image));
	if (_tmp_1 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	image_construct(_tmp_1, w, h, 255);
	Image *_ngg_tmp_4 = _tmp_1;
	if (this->preview) {
		image_destruct((Image *) (this->preview));
		free(this->preview);
	}

	this->preview = _ngg_tmp_4;

	Image *sbimg = (Image *) malloc(sizeof(Image));
	if (sbimg == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	image_construct(sbimg, w, SCRATCHBORDER, 255);
	image_fill_scratchpattern(sbimg);

	Image *_ngg_tmp_5 = sbimg;
	if (this->scratchborder) {
		image_destruct((Image *) (this->scratchborder));
		free(this->scratchborder);
	}

	this->scratchborder = _ngg_tmp_5;

	if (this->autocenter) {
		image_document_viewport_align_center(this);
	}
}

void image_document_viewport_zoom_at(ImageDocument *this, int x, int y, double zoom)
{
	image_document_viewport_zoom_at_internal(this, x, y, zoom);
	this->autocenter = false;
}

void image_document_viewport_zoom_at_internal(ImageDocument *this, int x, int y, double zoom)
{
	double srcx = image_document_translate_x(this, x);
	double srcy = image_document_translate_y(this, y);

	this->vpzoom = zoom;

	this->vpx = (int) (x - this->vpzoom * srcx);
	this->vpy = (int) (y - this->vpzoom * srcy);
}

void image_document_viewport_zoom_at_center(ImageDocument *this, double zoom)
{
	assert(this->preview);
	Image *preview = this->preview;
	image_document_viewport_zoom_at_internal(this, preview->width / 2, preview->height / 2, zoom);
}

void image_document_viewport_zoom_to_fit(ImageDocument *this)
{
	assert(this->preview);
	Image *preview = this->preview;
	assert(preview->width > 0);

	assert(preview->height > 0);
	double zbyw = preview->width / (double) (this->width);
	double zbyh = preview->height / (double) (this->height);

	this->vpzoom = dmin2(zbyw, zbyh);
	this->vpx = 0;
	this->vpy = 0;
	image_document_viewport_align_center(this);
}

_ngg_tuple_ImageDocument__update_composite image_document_update_composite(ImageDocument *this)
{
	if (!image_document_is_dirty(this)) {
		VaraRect defrect = vara_rect_default();
		return (_ngg_tuple_ImageDocument__update_composite) { false, defrect };
	}

	image_copy_image_region(this->composite, this->checkpattern, this->dirtyrect);
	image_document_composite_layerstack(this, this->composite, image_document_is_all_dirty(this), this->dirtyrect);

	if (DEBUG_COMPOSITE_DIRTYRECT) {
		print_region(this->dirtyrect, "composite dirtyrect");
		image_draw_debug_region(this->composite, this->dirtyrect);
	}

	VaraRect prvdirtyrect = this->dirtyrect;

	image_document_mark_not_dirty(this);

	return (_ngg_tuple_ImageDocument__update_composite) { true, prvdirtyrect };
}

VaraRect image_document_update_preview(ImageDocument *this)
{
	assert(this->preview);
	Image *preview = this->preview;

	_ngg_tuple_ImageDocument__update_composite _ngg_tmp_0 = image_document_update_composite(this);
	VaraRect compdirtyrect = _ngg_tmp_0.m1;
	_Bool isdirty = _ngg_tmp_0.m0;

	if (isdirty) {
		if (REPREVIEW_DIRTY_ONLY && SCALING_AA_ENABLE) {
			double border = this->vpzoom * SCALING_AA_RADIUS;
			compdirtyrect.x -= border;
			compdirtyrect.y -= border;
			compdirtyrect.w += 2 * border;
			compdirtyrect.h += 2 * border;
			compdirtyrect = image_clip_region(this->composite, compdirtyrect);
		}
	}

	_Bool geometry_changed = (((this->oldpreviewx != this->vpx || this->oldpreviewy != this->vpy) || this->oldprevieww != preview->width) || this->oldpreviewh != preview->height) || this->oldpreviewzoom != this->vpzoom;

	_Bool redraw_all = !REPREVIEW_DIRTY_ONLY || geometry_changed;

	if (this->preview_is_all_dirty) {
		redraw_all = true;
		this->preview_is_all_dirty = false;
	}

	if (redraw_all) {
		image_fill(preview, srgbpixel2linear(PIXEL_VPBG));
	}

	VaraRect prevdirtyrect = vara_rect_default();

	if (this->vpzoom == 1) {
		if (redraw_all) {
			prevdirtyrect = image_copy_image_with_destoffset(preview, this->composite, this->vpx, this->vpy);
		} else if (isdirty) {
			prevdirtyrect = image_copy_image_region_with_destoffset(preview, this->composite, compdirtyrect, this->vpx, this->vpy);
		}
	} else {
		if (redraw_all) {
			prevdirtyrect = image_copy_image_with_scale(preview, this->composite, this->vpx, this->vpy, this->vpzoom);
		} else if (isdirty) {
			prevdirtyrect = image_copy_image_region_with_scale(preview, this->composite, compdirtyrect, this->vpx, this->vpy, this->vpzoom);
		}
	}

	this->oldpreviewx = this->vpx;
	this->oldpreviewy = this->vpy;
	this->oldprevieww = preview->width;
	this->oldpreviewh = preview->height;

	this->oldpreviewzoom = this->vpzoom;

	if (this->scratchmode) {
		assert(this->scratchborder);
		Image *sbimg = this->scratchborder;

		image_copy_image_with_destoffset(preview, sbimg, 0, 0);
		image_copy_image_with_destoffset(preview, sbimg, 0, preview->height - SCRATCHBORDER);
	}

	if (redraw_all) {
		return image_get_region(preview);
	}

	return prevdirtyrect;
}

void image_document_redo(ImageDocument *this)
{
	if (!image_document_can_redo(this)) {
		vara_log_error("redo issued when it is disabled");
		return;
	}

	HistoryPoint *hp = history_point_stack_pop(this->redostack);

	if (hp->_ngg_clsid_final == &_ngg_clsid_ImageHistoryPoint) {
		history_point_stack_push(this->undostack, image_document_create_image_history_point(this));
	} else if (hp->_ngg_clsid_final == &_ngg_clsid_LayerStackHistoryPoint) {
		history_point_stack_push(this->undostack, image_document_create_layerstack_history_point(this));
	}

	image_document_apply_snapshot(this, hp);

	if (this->history_changed_cbk) {
		void (*cbknn)(void *userdata);
		cbknn = (void (*)(void *userdata)) (this->history_changed_cbk);
		cbknn(this->history_changed_userdata);
	}
}

void image_document_set_bgvisible(ImageDocument *this, _Bool visible)
{
	layer_set_visible(image_document_get_bglayer_must(this), visible);
	image_document_mark_all_dirty(this);
}

void image_document_set_curlayer_alpha(ImageDocument *this, double alpha)
{
	image_document_get_curlayer(this)->alpha = alpha;
	image_document_mark_all_dirty(this);
}

void image_document_set_fgvisible(ImageDocument *this, _Bool visible)
{
	layer_set_visible(image_document_get_fglayer_must(this), visible);
	image_document_mark_all_dirty(this);
}

void image_document_set_history_changed_callback(ImageDocument *this, void (*cbk)(void *userdata), void *userdata)
{
	this->history_changed_cbk = cbk;

	this->history_changed_userdata = userdata;
}

void image_document_toggle_scratch_mode(ImageDocument *this)
{
	if (this->scratchmode) {
		assert(this->prescratchbak);
		image_document_apply_snapshot(this, this->prescratchbak);
		if (this->prescratchbak) {
			((HistoryPoint *) (this->prescratchbak))->_ngg_vtab_history_point.destruct((HistoryPoint *) (this->prescratchbak));
			free(this->prescratchbak);
			this->prescratchbak = NULL;
		}
	} else {
		assert(!this->prescratchbak);
		HistoryPoint *_ngg_tmp_6 = image_document_create_image_history_point(this);
		if (this->prescratchbak) {
			((HistoryPoint *) (this->prescratchbak))->_ngg_vtab_history_point.destruct((HistoryPoint *) (this->prescratchbak));
			free(this->prescratchbak);
		}

		this->prescratchbak = _ngg_tmp_6;
	}

	this->scratchmode = !this->scratchmode;
	this->preview_is_all_dirty = true;
}

void image_fill_checkpattern(Image *img)
{
	LinearPixel lightpx = srgbpixel2linear(PIXEL_CHECKER_LIGHT);
	LinearPixel darkpx = srgbpixel2linear(PIXEL_CHECKER_DARK);

	for (int y = 0; y < img->height; y += 1) {
		for (int x = 0; x < img->width; x += 1) {
			_Bool islight = 0 != (x >> 3 & 1 ^ y >> 3 & 1);
			nan_img_set_linear_pixel(img, x, y, islight? lightpx: darkpx);
		}
	}
}

void image_fill_scratchpattern(Image *img)
{
	LinearPixel lightpx = srgbpixel2linear(PIXEL_SCRATCH_LIGHT);
	LinearPixel darkpx = srgbpixel2linear(PIXEL_SCRATCH_DARK);

	for (int y = 0; y < imin2(SCRATCHBORDER, img->height); y += 1) {
		for (int x = 0; x < img->width; x += 1) {
			int islight = (x + y) / SCRATCHBORDER & 1;

			nan_img_set_linear_pixel(img, x, y, islight? lightpx: darkpx);
		}
	}
}

void intvector_delete_item(intvector *iv, int idx)
{
	for (int i = idx; i < intvector_get_count(iv) - 1; i += 1) {
		intvector_set_item(iv, i, intvector_get_item(iv, i + 1));
	}

	intvector_pop(iv);
}

void intvector_swap_items(intvector *iv, int idx1, int idx2)
{
	int bak = intvector_get_item(iv, idx2);
	intvector_set_item(iv, idx2, intvector_get_item(iv, idx1));
	intvector_set_item(iv, idx1, bak);
}

VaraFileIOError write_image_to_file(Image *img, const char *fname)
{
	return write_image_to_png(img, fname);
}

void vara_log_error(const char *msg)
{
	fprintf(stderr, "vara: error: %s\n", msg);
}

_ngg_tuple_ImageDocument__update_composite _ngg_tuple_ImageDocument__update_composite_default()
{
	_ngg_tuple_ImageDocument__update_composite s;
	s.m0 = false;
	s.m1 = vara_rect_default();
	return s;
}
