/*
 * Copyright (c) 2002-2021 Julien Nadeau Carriere <vedge@csoft.net>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <agar/core.h>
#include <agar/gui.h>
#include <agar/map/map.h>

#include <string.h>

#if 0
static const AG_Version brushSetVersion = {
	"agar-map brush set",
	6, 0
};
#endif

typedef struct merge_tool {
	MAP_Tool tool;                            /* MAP_Tool -> MERGE_Tool */
	TAILQ_HEAD_(ag_object) brushes;           /* Brush maps */
	AG_Tlist *tlBrushes;                      /* Brush selection */
} MERGE_Tool;

enum {
	NODE, FILL,
	EDNW, EDNO, EDNE,
	EDWE, EDEA,
	EDSW, EDSO, EDSE
};

#if 0
static const int
MergeTable[9][9] = {
       /* nw    n     ne    w     fill  e     sw    s     se */
	{ EDNW, EDNO, EDNO, EDWE, NODE, FILL, FILL, FILL, FILL }, /* nw */
	{ EDNO, EDNO, EDNO, FILL, NODE, FILL, FILL, FILL, FILL }, /* n */
	{ EDNO, EDNO, NODE, 0   , NODE, EDEA, FILL, FILL, EDEA }, /* ne */
	{ EDWE, FILL, FILL, EDWE, NODE, FILL, EDWE, FILL, FILL }, /* w */
	{ FILL, FILL, FILL, FILL, NODE, FILL, FILL, FILL, FILL }, /* fill */
	{ FILL, FILL, EDEA, FILL, NODE, EDEA, FILL, FILL, EDEA }, /* e */
	{ EDWE, 0,    EDEA, EDWE, NODE, 0,    EDSW, EDSO, EDSO }, /* sw */
	{ FILL, FILL, FILL, FILL, NODE, FILL, EDSO, EDSO, EDSO }, /* s */
	{ EDWE, 0,    EDEA, EDNO, NODE, EDNO, EDSO, EDSO, EDSE }  /* se */
};
#endif

static void
FreeBrushes(MAP_Tool *_Nonnull t)
{
	MERGE_Tool *mt = t;
	AG_Object *ob, *obNext;

	for (ob = TAILQ_FIRST(&mt->brushes);
	     ob != TAILQ_END(&mt->brushes);
	     ob = obNext) {
		obNext = TAILQ_NEXT(ob, cobjs);
		AG_ObjectDestroy(ob);
	}
	TAILQ_INIT(&mt->brushes);
}

static void
Destroy(MAP_Tool *_Nonnull t)
{
	FreeBrushes(t);
}

static void
CreateBrush(AG_Event *_Nonnull event)
{
	char name[AG_OBJECT_NAME_MAX];
	char mapName[AG_OBJECT_NAME_MAX];
	MERGE_Tool *mt = AG_PTR(1);
	AG_Textbox *tbName = AG_TEXTBOX_PTR(2);
	MAP *m;

	AG_TextboxCopyString(tbName, name, sizeof(name) - sizeof("brush()"));
	if (name[0] == '\0') {
		AG_TextMsg(AG_MSG_ERROR, _("No brush name was given."));
		return;
	}
	
	Snprintf(mapName, sizeof(mapName), "brush(%s)", name);
	if (AG_TlistFindText(mt->tlBrushes, mapName) != NULL) {
		AG_TextMsg(AG_MSG_ERROR, _("A `%s' brush exists."), mapName);
		return;
	}

	m = MAP_New(NULL, mapName);
	if (AG_ObjectLoad(m) == -1) {
		if (MAP_AllocNodes(map, 4,4) == -1) {
			AG_TextMsg(AG_MSG_ERROR, "MAP_AllocNodes: %s", AG_GetError());
			goto fail;
		}
	}

	TAILQ_INSERT_HEAD(&mt->brushes, OBJECT(m), cobjs);

	AG_TlistDeselectAll(mt->tlBrushes);
	AG_TlistSelect(mt->tlBrushes,
	    AG_TlistAddPtrHead(mt->tlBrushes, NULL, mapName, m));

	AG_TextboxPrintf(tbName, NULL);
	return;
fail:
	AG_ObjectDestroy(m);
}

static void
EditBrush(AG_Event *_Nonnull event)
{
	AG_TlistItem *it;
	MERGE_Tool *mt = AG_PTR(1);
	AG_Window *pwin = AG_WINDOW_PTR(2);

	TAILQ_FOREACH(it, &mt->tlBrushes->items, items) {
		MAP *brush = it->p1;
		AG_Window *win;
		AG_Toolbar *tbar;

		if (!it->selected)
			continue;

		if ((win = AG_WindowNewNamed(0, "mapedit-tool-merge-%s",
		    OBJECT(brush)->name)) == NULL)
			continue;

		tbar = AG_ToolbarNew(win, AG_TOOLBAR_HORIZ, 1, 0);

		MAP_ViewNew(win, brush,
		    MAP_VIEW_EDIT | MAP_VIEW_GRID | MAPVIEW_PROPS,
		    tbar, NULL);

		AG_WindowAttach(pwin, win);
		AG_WindowShow(win);
	}
}

static void
RemoveBrush(AG_Event *_Nonnull event)
{
	MERGE_Tool *mt = AG_PTR(1);
	AG_TlistItem *it, *nit;

	for (it = TAILQ_FIRST(&mt->tlBrushes->items);
	     it != TAILQ_END(&mt->tlBrushes->items);
	     it = nit) {
		nit = TAILQ_NEXT(it, items);
		if (it->selected) {
			char wname[AG_OBJECT_NAME_MAX];
			AG_Object *brush = it->p1;
			AG_Window *win;

			Snprintf(wname, sizeof(wname),
			    "win-mapedit-tool-merge-%s",
			    OBJECT(brush)->name);
			if ((win = AG_FindWindow(wname)) != NULL) {
				AG_WindowHide(win);
				AG_ObjectDetach(win);
			}

			TAILQ_REMOVE(&brushes, brush, cobjs);
			AG_TlistDel(mt->tlBrushes, it);
			AG_ObjectDestroy(brush);
		}
	}
}

static void
Interpolate(MAP *_Nonnull mapSrc, MAP_Node *_Nonnull nodeSrc, MAP_Item *_Nonnull miSrc,
    MAP *_Nonnull mapDst, MAP_Node *_Nonnull nodeDest, MAP_Item *_Nonnull miDst)
{
	/* TODO */
}

static int
Effect(MAP_Tool *_Nonnull t, MAP_Node *_Nonnull n)
{
	MERGE_Tool *mt = t;
	MAP_View *mv = t->mv;
	MAP *map = mv->map;
	AG_TlistItem *it;
	
	/* Avoid circular references. XXX ugly */
	if (strncmp(OBJECT(m)->name, "brush(", 6) == 0)
		return (1);
	
	TAILQ_FOREACH(it, &mt->tlBrushes->items, items) {
		MAP *mapSrc;
		int sx, sy, dx, dy;

		if (!it->selected)
			continue;

		mapSrc = it->p1;
		for (sy = 0, dy = mv->cy;
		     sy < mapSrc->h && dy < map->h;
		     sy++, dy++) {
			for (sx = 0, dx = mv->cx;
			     sx < mapSrc->w && dx < map->w;
			     sx++, dx++) {
				MAP_Node *nodeSrc = &mapSrc->map[sy][sx];
				MAP_Node *nodeDst = &map->map[dy][dx];
				MAP_Item *miSrc, *miDst;

				TAILQ_FOREACH(miSrc, &nodeSrc->items, items) {
					TAILQ_FOREACH(miDst, &nodeDst->items, items) {
						Interpolate(
						    mapSrc, nodeSrc, miSrc,
						    m,      nodeDst, miDst);
					}
				}
			}
		}
	}
	return (1);
}

#if 0
static int
Load(MAP_Tool *_Nonnull t, AG_DataSource *_Nonnull buf)
{
	MERGE_Tool *mt = t;
	Uint32 i, nBrushes;
	
	if (AG_ReadVersion(buf, &brushSetVersion, NULL) != 0)
		return (-1);

	FreeBrushes(t);
	AG_TlistClear(mt->tlBrushes);

	nBrushes = AG_ReadUint32(buf);
	for (i = 0; i < nBrushes; i++) {
		char mapName[AG_OBJECT_NAME_MAX];
		MAP *map;

		AG_CopyString(mapName, buf, sizeof(mapName));
		m = Malloc(sizeof(MAP));
		MAP_Init(map, mapName);
		map_load(map, buf);

		TAILQ_INSERT_HEAD(&mt->brushes, OBJECT(nbrush), cobjs);
		AG_TlistAddPtrHead(mt->tlBrushes, NULL, mapName, nbrush);
	}
	return (0);
}

static int
Save(MAP_Tool *_Nonnull t, AG_DataSource *_Nonnull buf)
{
	MERGE_Tool *mt = t;
	AG_Object *ob;
	Uint32 nBrushes = 0;
	off_t count_offs;

	AG_WriteVersion(buf, &brushSetVersion);
	count_offs = AG_Tell(buf);			/* Skip count */
	AG_WriteUint32(buf, 0);	
	TAILQ_FOREACH(ob, &mt->brushes, cobjs) {
		struct brush *brush = (struct brush *)ob;

		AG_WriteString(buf, ob->name);
		map_save(brush, buf);
		nBrushes++;
	}
	AG_WriteUint32At(buf, nBrushes, count_offs);	/* Write count */
	return (0);
}
#endif

static int
Cursor(MAP_Tool *_Nonnull t, AG_Rect *_Nonnull rd)
{
#if 0
	MERGE_Tool *mt = t;
	MAP_View *mv = t->mv;
	MAP *map = mv->map, *mapSrc;
	AG_TlistItem *it;
	int sx,sy, dx,dy;
	int rv = -1;
	const int tileSz = MAPTILESZ(mv);
	
	/* XXX ugly work around circular ref */
	if (strncmp(OBJECT(map)->name, "brush(", 6) == 0)
		return (-1);

	TAILQ_FOREACH(it, &mt->tlBrushes->items, items) {
		if (!it->selected) {
			continue;
		}
		mapSrc = it->p1;
		for (sy = 0, dy = rd->y;
		     sy < mapSrc->h;
		     sy++, dy += tileSz) {
			for (sx = 0, dx = rd->x;
			     sx < mapSrc->w;
			     sx++, dx += tileSz) {
				MAP_Node *nodeSrc = &mapSrc->map[sy][sx];
				MAP_Item *mi;

				TAILQ_FOREACH(mi, &nodeSrc->items, items) {
					MAP_ItemDraw(map, mi,
					    WIDGET(mv)->rView.x1 + dx,
					    WIDGET(mv)->rView.y1 + dy,
					    mv->cam);
					rv = 0;
				}
				if (mv->flags & MAPVIEW_PROPS)
					MAP_ViewDraw_props(mv, nodeSrc, dx,dy,
					    -1, -1);
			}
		}
	}
	return (rv);
#else
	return (-1);
#endif
}

static void
Init(MAP_Tool *_Nonnull t)
{
	MERGE_Tool *mt = t;
	AG_Window *win;
	AG_Box *hb;
	AG_Button *bu;
	AG_Textbox *tb;

	if ((win = MAP_ToolWindow(t, "mapedit-tool-merge")) == NULL)
		return;

	hb = AG_BoxNewHoriz(win, AG_BOX_HFILL);
	{
		tb = AG_TextboxNewS(hb, AG_TEXTBOX_FOCUS, _("Name: "));
		AG_SetEvent(tb, "textbox-return", CreateBrush, "%p,%p", t, tb);

		bu = AG_ButtonNew(hb, 0, _("Create"));
		AG_ButtonSetPadding(bu, 6);			/* Align */
		AG_ButtonSetFocusable(bu, 0);
		AG_SetEvent(bu, "button-pushed", CreateBrush, "%p,%p", t, tb);
	}

	hb = AG_BoxNewHoriz(win, AG_BOX_HOMOGENOUS | AG_BOX_HFILL);
	{
#if 0
		bu = AG_ButtonNew(hb, 0, _("Load set"));
		AG_SetEvent(bu, "button-pushed", LoadBrushes, NULL);
		bu = AG_ButtonNew(hb, 0, _("Save set"));
		AG_SetEvent(bu, "button-pushed", SaveBrushes, NULL);
#endif
		bu = AG_ButtonNew(hb, 0, _("Edit"));
		AG_SetEvent(bu, "button-pushed", EditBrush, "%p,%p", t, win);
		bu = AG_ButtonNew(hb, 0, _("Remove"));
		AG_SetEvent(bu, "button-pushed", RemoveBrush, "%p", t);
	}

	TAILQ_INIT(&mt->brushes);

	mt->tlBrushes = AG_TlistNew(win, AG_TLIST_MULTI | AG_TLIST_EXPAND);
}


const MAP_Tool mapMergeOps = {
	"Merge",
	N_("Merge Pattern"),
	&mapIconMerge,
	sizeof(MERGE_Tool),
	0,
	Init,
	Destroy,
	NULL,			/* pane */
	NULL,			/* edit */
	Cursor,
	Effect,
	NULL,			/* mousemotion */
	NULL,			/* mousebuttondown */
	NULL,			/* mousebuttonup */
	NULL,			/* keydown */
	NULL,			/* keyup */
	NULL			/* pane */
};
