/* procedural.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 "procedural.h"


void instruction_owning_vector_destruct(InstructionOwningVector *this)
{
	instruction_owning_vector_delete_all(this);
	if (this->arr) {
		free(this->arr);
	}
}

void instruction_owning_vector__resize(InstructionOwningVector *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 = (Instruction * *) realloc(this->arr, this->alloccount * sizeof(this->arr[1u]));
		if (this->arr == NULL) {
			perror(NULL);
			exit(EXIT_FAILURE);
		}
	}

	this->count = newcount;
}

void instruction_owning_vector_append(InstructionOwningVector *this, Instruction *newitem)
{
	int newcount = 1 + this->count;

	instruction_owning_vector__resize(this, newcount);

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

void instruction_owning_vector_clear(InstructionOwningVector *this)
{
	instruction_owning_vector_delete_all(this);

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

	this->alloccount = 8;
}

void instruction_owning_vector_delete_all(InstructionOwningVector *this)
{
	while (this->count > 0) {
		Instruction *obj = instruction_owning_vector_pop(this);
		if (obj) {
			obj->_ngg_vtab_instruction.destruct(obj);
			free(obj);
		}
	}
}

Instruction * instruction_owning_vector_get_item(InstructionOwningVector *this, int index)
{
	assert(index < this->count);

	return this->arr[index];
}

Instruction * instruction_owning_vector_pop(InstructionOwningVector *this)
{
	assert(this->count > 0);
	Instruction *r = instruction_owning_vector_get_item(this, this->count - 1);
	this->count = this->count - 1;

	return r;
}

void instruction_owning_vector_set_item(InstructionOwningVector *this, int index, Instruction *itm)
{
	assert(index < this->count);

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

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

int instruction_owning_vector_get_count(InstructionOwningVector *this)
{
	return this->count;
}

_Bool instruction_owning_vector_is_empty(InstructionOwningVector *this)
{
	return 0 == this->count;
}

void instruction_owning_vector_construct(InstructionOwningVector *this)
{
	this->alloccount = 8;
	Instruction * *_tmp_1 = (Instruction * *) calloc((size_t) 8, sizeof(Instruction *));
	if (_tmp_1 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

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

void procedural_context_execute_all(ProceduralContext *this)
{
	while (procedural_context_execute_next(this)) {
	}
}

_Bool procedural_context_execute_next(ProceduralContext *this)
{
	if (this->counter >= instruction_owning_vector_get_count(this->iov)) {
		return false;
	}

	Instruction *inst = instruction_owning_vector_get_item(this->iov, this->counter);

	if (inst->_ngg_clsid_final == &_ngg_clsid_DrawDotPerc) {
		DrawDotPerc *finst = (DrawDotPerc *) inst;
		mchelper_draw_dot_perc_nongui(this->doc, finst->x, finst->y, finst->p);
	} else if (inst->_ngg_clsid_final == &_ngg_clsid_DrawLinePerc) {
		DrawLinePerc *finst = (DrawLinePerc *) inst;
		mchelper_draw_line_perc_nongui(this->doc, finst->x1, finst->y1, finst->p1, finst->x2, finst->y2, finst->p2);
	} else if (inst->_ngg_clsid_final == &_ngg_clsid_DrawVgradient) {
		DrawVgradient *finst = (DrawVgradient *) inst;
		mchelper_draw_vgradient_nongui(this->doc, finst->yfrom, finst->yto, finst->stp, finst->rfac, finst->gfac, finst->bfac, finst->opacity, finst->absolutergb);
	} else if (inst->_ngg_clsid_final == &_ngg_clsid_SelectBrushTool) {
		mchelper_pick_brush_tool_nongui(this->doc);
	} else if (inst->_ngg_clsid_final == &_ngg_clsid_SelectPencilBrushPreset) {
		this->doc->drawparams.brushparams = prebrush_pencil();
	} else if (inst->_ngg_clsid_final == &_ngg_clsid_SelectRoundBrushPreset) {
		this->doc->drawparams.brushparams = prebrush_round();
	} else if (inst->_ngg_clsid_final == &_ngg_clsid_SelectSoftRoundBrushPreset) {
		this->doc->drawparams.brushparams = prebrush_soft_round();
	} else if (inst->_ngg_clsid_final == &_ngg_clsid_SetBrushOpacity) {
		SetBrushOpacity *finst = (SetBrushOpacity *) inst;
		this->doc->drawparams.brushparams.opacity = finst->opacity;
	} else if (inst->_ngg_clsid_final == &_ngg_clsid_SetBrushPressureForSize) {
		SetBrushPressureForSize *finst = (SetBrushPressureForSize *) inst;
		this->doc->drawparams.brushparams.pressure_size = finst->use_pressure;
	} else if (inst->_ngg_clsid_final == &_ngg_clsid_SetFillSizePerc) {
		SetFillSizePerc *finst = (SetFillSizePerc *) inst;
		mchelper_set_fill_size_perc_nongui(this->doc, finst->sizeperc);
	} else if (inst->_ngg_clsid_final == &_ngg_clsid_SetStrokeColorSrgb) {
		SetStrokeColorSrgb *finst = (SetStrokeColorSrgb *) inst;
		mchelper_set_stroke_color_nongui(this->doc, finst->r, finst->g, finst->b);
	} else {
		assert(false);
	}

	this->counter += 1;

	return true;
}

void procedural_context_queue(ProceduralContext *this, Instruction *inst)
{
	instruction_owning_vector_append(this->iov, inst);
}

void procedural_context_construct(ProceduralContext *this, ImageDocument *doc)
{
	InstructionOwningVector *_tmp_1 = (InstructionOwningVector *) malloc(sizeof(InstructionOwningVector));
	if (_tmp_1 == NULL) {
		perror(NULL);
		exit(EXIT_FAILURE);
	}

	instruction_owning_vector_construct(_tmp_1);
	this->iov = _tmp_1;
	this->counter = 0;
	this->doc = doc;
}

void procedural_context_destruct(ProceduralContext *this)
{
	if (this->iov) {
		instruction_owning_vector_destruct(this->iov);
		free(this->iov);
	}
}

void mchelper_draw_dot_perc_nongui(ImageDocument *doc, double x, double y, double pressure)
{
	mchelper_draw_line_perc_nongui(doc, x, y, pressure, x, y, pressure);
}

void mchelper_draw_line_perc_nongui(ImageDocument *doc, double x1, double y1, double p1, double x2, double y2, double p2)
{
	doc->drawparams.oldpressure = p1;

	doc->drawparams.pressure = p2;

	VaraRect rect = image_line(image_document_get_curlayer(doc)->img, roundedpercsiz(x1, image_document_get_width(doc)), roundedpercsiz(y1, image_document_get_height(doc)), roundedpercsiz(x2, image_document_get_width(doc)), roundedpercsiz(y2, image_document_get_height(doc)), doc->drawparams);

	image_document_add_dirty_region(doc, rect);
}

void mchelper_set_fill_size_perc_nongui(ImageDocument *doc, double siz)
{
	doc->drawparams.brushparams.siz = roundedpercsiz(siz, image_document_get_width(doc));
}

void mchelper_set_stroke_color_nongui(ImageDocument *doc, float r, float g, float b)
{
	doc->drawparams.fgcolor = srgbpixel2linear((Pixel) { { r, g, b, 1 } });
}

void mchelper_draw_vgradient_nongui(ImageDocument *doc, double yfrom, double yto, double stp, double rfac, double gfac, double bfac, double opacity, _Bool absolutergb)
{
	assert(yfrom <= yto);

	int iyfrom = (int) (yfrom * 100);
	int iyto = (int) (yto * 100);
	int istp = (int) (stp * 100);

	doc->drawparams.brushparams = prebrush_soft_round();

	doc->drawparams.brushparams.opacity *= opacity;

	mchelper_set_fill_size_perc_nongui(doc, 1.5 * stp);

	for (int y = iyfrom; y <= iyto; y += istp) {
		double loopfac = (y - iyfrom) / ((double) iyto - iyfrom);
		double yfac = y / 100.0;

		if (absolutergb) {
			mchelper_set_stroke_color_nongui(doc, rfac, gfac, bfac);
		} else {
			mchelper_set_stroke_color_nongui(doc, rfac * loopfac, gfac * loopfac, bfac * loopfac);
		}

		mchelper_draw_line_perc_nongui(doc, 0, yfac, 1, 1, yfac, 1);
	}
}

void mchelper_pick_brush_tool_nongui(ImageDocument *doc)
{
	doc->drawparams.active_tool = TOOL_BRUSH;
	doc->drawparams.erase = false;
}
