/*
 *                            COPYRIGHT
 *
 *  camv-rnd - electronics-related CAM viewer
 *  Copyright (C) 2019,2020,2023 Tibor 'Igor2' Palinkas
 *  (copied from pcb-rnd by the author)
 *
 *  This program 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 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 31 Milk Street, # 960789 Boston, MA 02196 USA.
 *
 *  Contact:
 *    Project page: http://repo.hu/projects/camv-rnd
 *    lead developer: http://repo.hu/projects/camv-rnd/contact.html
 *    mailing list: pcb-rnd (at) list.repo.hu (send "subscribe")
 */

#include "config.h"

#include <librnd/config.h>

#include <genvector/vti0.h>
#include <genvector/vtp0.h>

#include <librnd/hid/hid.h>
#include <librnd/core/hid_cfg.h>
#include <librnd/hid/hid_dad.h>

#include <librnd/core/actions.h>
#include "data.h"
#include "event.h"
#include <librnd/core/rnd_conf.h>

#include "layersel.h"

/* Enable this to make sublayers and layer indices visible */
#if 0
#define LAYERSEL_DEBUG
#endif

static const char *xpm_up[] = {
"10 10 3 1",
" 	c None",
"@	c #6EA5D7",
"+	c #000000",
"    ++    ",
"   +@@+   ",
"  +@@@@+  ",
" +@@@@@@+ ",
"+@@@@@@@@+",
"++++@@++++",
"   +@@+   ",
"   +@@+   ",
"   +@@+   ",
"   ++++   ",
};

static const char *xpm_upmost[] = {
"10 10 3 1",
" 	c None",
"@	c #6EA5D7",
"+	c #000000",
"++++++++++",
"+@@@@@@@@+",
"++++@@++++",
" +@@@@@@+ ",
"+@@@@@@@@+",
"++++@@++++",
"   +@@+   ",
"   +@@+   ",
"   +@@+   ",
"   ++++   ",
};

static const char *xpm_down[] = {
"10 10 3 1",
" 	c None",
"@	c #6EA5D7",
"+	c #000000",
"   ++++   ",
"   +@@+   ",
"   +@@+   ",
"   +@@+   ",
"++++@@++++",
"+@@@@@@@@+",
" +@@@@@@+ ",
"  +@@@@+  ",
"   +@@+   ",
"    ++    ",
};

static const char *xpm_downmost[] = {
"10 10 3 1",
" 	c None",
"@	c #6EA5D7",
"+	c #000000",
"   ++++   ",
"   +@@+   ",
"   +@@+   ",
"   +@@+   ",
"++++@@++++",
"+@@@@@@@@+",
" +@@@@@@+ ",
"++++@@++++",
"+@@@@@@@@+",
"++++++++++",
};

static const char *xpm_add[] = {
"10 10 3 1",
" 	c None",
"@	c #6EA5D7",
"+	c #000000",
"   ++++   ",
"   +@@+   ",
"   +@@+   ",
"++++@@++++",
"+@@@@@@@@+",
"+@@@@@@@@+",
"++++@@++++",
"   +@@+   ",
"   +@@+   ",
"   ++++   ",
};

static const char *xpm_del[] = {
"10 10 3 1",
" 	c None",
"@	c #6EA5D7",
"+	c #000000",
"          ",
"          ",
"          ",
"++++++++++",
"+@@@@@@@@+",
"+@@@@@@@@+",
"++++++++++",
"          ",
"          ",
"          ",
};


static const char *all_vis_xpm[] = {
"12 9 3 1",
" 	c None",
"@	c #6EA5D7",
"+	c #000000",
"            ",
"    ++++    ",
"  ++@@@@++  ",
" +@@@++@@@+ ",
"+@@@++++@@@+",
" +@@@++@@@+ ",
"  ++@@@@++  ",
"    ++++    ",
"            ",
};

static const char *all_invis_xpm[] = {
"12 9 3 1",
" 	c None",
"@	c #6EA5D7",
"+	c #000000",
" @+@        ",
"  @+@+++    ",
"  +@+@@@++  ",
" +@@@+@@@@+ ",
"+@@@+@+@@@@+",
" +@@@+@+@@+ ",
"  ++@@@@+@  ",
"    ++++@+@ ",
"         @+@",
};


typedef struct {
	char buf[32][20];
	const char *xpm[32];
} gen_xpm_t;

typedef struct layersel_ctx_s layersel_ctx_t;

typedef struct {
	int wvis_on, wvis_off, wlab;
	int wunsel, wsel;
	gen_xpm_t on, off;
	rnd_cardinal_t lid;
	layersel_ctx_t *ls;
} ls_layer_t;


struct layersel_ctx_s {
	rnd_hid_dad_subdialog_t sub;
	camv_design_t *camv;
	int sub_inited;
	int wbut_up, wbut_down, wbut_top, wbut_bottom, wbut_load, wbut_remove, wbut_vis, wbut_invis;
	int lock_vis, lock_sel;
	vtp0_t layers; /* -> ls_layer_t */
};

static layersel_ctx_t layersel;

static void lys_update_vis(camv_design_t *camv, ls_layer_t *lys)
{
	camv_layer_t **ly;
	int hide_on = 1, hide_off = 1;

	if (lys == NULL)
		return;

	ly = (camv_layer_t **)vtp0_get(&camv->layers, lys->lid, 0);
	if ((ly != NULL) && (*ly != NULL)) {
		hide_on = !(*ly)->vis;
		hide_off = !!(*ly)->vis;
	}

	rnd_gui->attr_dlg_widget_hide(lys->ls->sub.dlg_hid_ctx, lys->wvis_on, hide_on);
	rnd_gui->attr_dlg_widget_hide(lys->ls->sub.dlg_hid_ctx, lys->wvis_off, hide_off);
}

static void layer_vis_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	ls_layer_t *lys = attr->user_data;
	camv_design_t *camv = lys->ls->camv;
	camv_layer_t **ly = (camv_layer_t **)vtp0_get(&camv->layers, lys->lid, 0);

	if ((ly == NULL) || (*ly == NULL))
		return;

	lys->ls->lock_vis++;
	camv_layer_set_vis(camv, lys->lid, !(*ly)->vis, 1);
	lys->ls->lock_vis--;

	lys_update_vis(camv, lys);
	camv_hid_redraw(camv);
}

static void layer_unselect(layersel_ctx_t *ls)
{
	ls_layer_t **lys;
	if (ls->camv->lysel < 0)
		return;

	lys = (ls_layer_t **)vtp0_get(&ls->layers, ls->camv->lysel, 0);
	if ((lys != NULL) && (*lys != NULL))
		rnd_gui->attr_dlg_widget_state(ls->sub.dlg_hid_ctx, (*lys)->wlab, 1);

	camv_layer_select(ls->camv, -1);
}

static void layer_select_idx(layersel_ctx_t *ls, int idx)
{
	ls_layer_t **lys;
	layer_unselect(ls);
	lys = (ls_layer_t **)vtp0_get(&ls->layers, idx, 0);
	if (lys != NULL)
		rnd_gui->attr_dlg_widget_state(ls->sub.dlg_hid_ctx, (*lys)->wlab, 2);
	else
		idx = -1;
	camv_layer_select(layersel.camv, idx);
}

static void layer_select(ls_layer_t *lys)
{
	layer_select_idx(lys->ls, lys->lid);
}


static void layer_sel_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	layer_select((ls_layer_t *)attr->user_data);
}


static void layer_right_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	layer_select((ls_layer_t *)attr->user_data);
	rnd_actionl("Popup", "layer", NULL);
}

static void layer_button_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	layersel_ctx_t *ls = attr->user_data;
	int wid = attr - ls->sub.dlg;
	if (wid == ls->wbut_up)          rnd_actionl("Layer", "up", NULL);
	else if (wid == ls->wbut_down)   rnd_actionl("Layer", "down", NULL);
	else if (wid == ls->wbut_top)    rnd_actionl("Layer", "top", NULL);
	else if (wid == ls->wbut_bottom) rnd_actionl("Layer", "bottom", NULL);
	else if (wid == ls->wbut_load)   { rnd_actionl("Load", "Layer", NULL) || rnd_actionva(&camv.hidlib, "Zoom", "auto_first", NULL); }
	else if (wid == ls->wbut_remove) rnd_actionl("Layer", "del", NULL);
	else if (wid == ls->wbut_vis)    rnd_actionl("Layer", "all-visible", NULL);
	else if (wid == ls->wbut_invis)  rnd_actionl("Layer", "all-invisible", NULL);
	else rnd_message(RND_MSG_ERROR, "Internal error: layer_button_cb(): invalid wid\n");
}

/* draw a visibility box: filled or partially filled with layer color */
static void layer_vis_box(gen_xpm_t *dst, int filled, const rnd_color_t *color, int brd, int hatch, int width, int height, int slant)
{
	int max_height = height;
	char *p;
	unsigned int w, line = 0, n;

	rnd_snprintf(dst->buf[line++], 20, "%d %d 4 1", width, height);
	strcpy(dst->buf[line++], ".	c None");
	strcpy(dst->buf[line++], "u	c None");
	strcpy(dst->buf[line++], "b	c #000000");
	rnd_snprintf(dst->buf[line++], 20, "c	c #%02X%02X%02X", color->r, color->g, color->b);

	while (height--) {
		w = width;
		p = dst->buf[line++];
		while (w--) {
			if ((height < brd) || (height >= max_height-brd) || (w < brd) || (w >= width-brd))
				*p = 'b'; /* frame */
			else if ((hatch) && (((w - height) % 4) == 0))
				*p = '.';
			else if ((width-w+slant < height) || (filled))
				*p = 'c'; /* layer color fill (full or up-left triangle) */
			else
				*p = 'u'; /* the unfilled part when triangle should be transparent */
			p++;
		}
		*p = '\0';
	}

	for(n=0; n < line; n++)
		dst->xpm[n] = dst->buf[n];
}

static void layersel_create_layer(layersel_ctx_t *ls, ls_layer_t *lys, int lidx, const char *short_name, const char *name, const rnd_color_t *color, int brd, int hatch)
{
	if (name == NULL)
		name = "<sub>";

	if (short_name == NULL) {
		short_name = strrchr(name, '/');
		if (short_name == NULL)
			short_name = name;
		else
			short_name++;
	}

	layer_vis_box(&lys->on, 1, color, brd, hatch, 16, 16, 5);
	layer_vis_box(&lys->off, 0, color, brd, hatch, 16, 16, 5);

	RND_DAD_BEGIN_HBOX(ls->sub.dlg);
		RND_DAD_PICTURE(ls->sub.dlg, lys->on.xpm);
			lys->wvis_on = RND_DAD_CURRENT(ls->sub.dlg);
			RND_DAD_SET_ATTR_FIELD(ls->sub.dlg, user_data, lys);
			RND_DAD_CHANGE_CB(ls->sub.dlg, layer_vis_cb);
		RND_DAD_PICTURE(ls->sub.dlg, lys->off.xpm);
			lys->wvis_off = RND_DAD_CURRENT(ls->sub.dlg);
			RND_DAD_SET_ATTR_FIELD(ls->sub.dlg, user_data, lys);
			RND_DAD_CHANGE_CB(ls->sub.dlg, layer_vis_cb);
#ifdef LAYERSEL_DEBUG
		{
			char idx[32];
			sprintf(idx, "{%d}", lidx);
			RND_DAD_LABEL(ls->sub.dlg, idx);
		}
#endif
		RND_DAD_LABEL(ls->sub.dlg, short_name);
			lys->wlab = RND_DAD_CURRENT(ls->sub.dlg);
			RND_DAD_SET_ATTR_FIELD(ls->sub.dlg, user_data, lys);
			RND_DAD_CHANGE_CB(ls->sub.dlg, layer_sel_cb);
			RND_DAD_RIGHT_CB(ls->sub.dlg, layer_right_cb);
			RND_DAD_HELP(ls->sub.dlg, name);
	RND_DAD_END(ls->sub.dlg);
}

static void layersel_docked_create(layersel_ctx_t *ls, camv_design_t *camv)
{
	long n, i; /* must be signed */

	RND_DAD_BEGIN_VBOX(ls->sub.dlg);
		RND_DAD_COMPFLAG(ls->sub.dlg, RND_HATF_EXPFILL | RND_HATF_SCROLL);

		/* build layers in reverse order (rendering needs to be fast so goes from front to back) */
		for(i = 0, n = camv->layers.used-1; n >= 0; n--) {
			camv_layer_t *ly = camv->layers.array[n];
			ls_layer_t *lys, **lysp;

			lysp = (ls_layer_t **)vtp0_get(&ls->layers, n, 1);

#ifndef LAYERSEL_DEBUG
			if (ly->sub) {
				if ((lysp != NULL) && (*lysp != NULL)) {
					free(*lysp);
					*lysp = NULL;
				}
				continue;
			}
#endif
			if (*lysp == NULL)
				*lysp = calloc(sizeof(ls_layer_t), 1);
			lys = *lysp;
			lys->lid = n;
			lys->ls = ls;
			layersel_create_layer(ls, lys, n, ly->short_name, ly->name, &ly->color, 1, 0);
			i++;
		}

		RND_DAD_BEGIN_VBOX(ls->sub.dlg);
			RND_DAD_COMPFLAG(ls->sub.dlg, RND_HATF_EXPFILL);
		RND_DAD_END(ls->sub.dlg);


		RND_DAD_BEGIN_HBOX(ls->sub.dlg);
			RND_DAD_PICBUTTON(ls->sub.dlg, xpm_up);
				RND_DAD_HELP(ls->sub.dlg, "Move layer up in the stack");
				RND_DAD_CHANGE_CB(ls->sub.dlg, layer_button_cb);
				RND_DAD_SET_ATTR_FIELD(ls->sub.dlg, user_data, ls);
				ls->wbut_up = RND_DAD_CURRENT(ls->sub.dlg);
			RND_DAD_PICBUTTON(ls->sub.dlg, xpm_down);
				RND_DAD_HELP(ls->sub.dlg, "Move layer down in the stack");
				RND_DAD_CHANGE_CB(ls->sub.dlg, layer_button_cb);
				RND_DAD_SET_ATTR_FIELD(ls->sub.dlg, user_data, ls);
				ls->wbut_down = RND_DAD_CURRENT(ls->sub.dlg);
			RND_DAD_PICBUTTON(ls->sub.dlg, xpm_upmost);
				RND_DAD_HELP(ls->sub.dlg, "Move layer to the top of the stack");
				RND_DAD_CHANGE_CB(ls->sub.dlg, layer_button_cb);
				RND_DAD_SET_ATTR_FIELD(ls->sub.dlg, user_data, ls);
				ls->wbut_top = RND_DAD_CURRENT(ls->sub.dlg);
			RND_DAD_PICBUTTON(ls->sub.dlg, xpm_downmost);
				RND_DAD_HELP(ls->sub.dlg, "Move layer to the bottom of the stack");
				RND_DAD_CHANGE_CB(ls->sub.dlg, layer_button_cb);
				RND_DAD_SET_ATTR_FIELD(ls->sub.dlg, user_data, ls);
				ls->wbut_bottom = RND_DAD_CURRENT(ls->sub.dlg);
			RND_DAD_PICBUTTON(ls->sub.dlg, xpm_add);
				RND_DAD_HELP(ls->sub.dlg, "Add (load) a new layer");
				RND_DAD_CHANGE_CB(ls->sub.dlg, layer_button_cb);
				RND_DAD_SET_ATTR_FIELD(ls->sub.dlg, user_data, ls);
				ls->wbut_load = RND_DAD_CURRENT(ls->sub.dlg);
			RND_DAD_PICBUTTON(ls->sub.dlg, xpm_del);
				RND_DAD_HELP(ls->sub.dlg, "Del (remove) a layer");
				RND_DAD_CHANGE_CB(ls->sub.dlg, layer_button_cb);
				RND_DAD_SET_ATTR_FIELD(ls->sub.dlg, user_data, ls);
				ls->wbut_remove = RND_DAD_CURRENT(ls->sub.dlg);
			RND_DAD_PICBUTTON(ls->sub.dlg, all_vis_xpm);
				RND_DAD_HELP(ls->sub.dlg, "all layers visible");
				RND_DAD_CHANGE_CB(ls->sub.dlg, layer_button_cb);
				RND_DAD_SET_ATTR_FIELD(ls->sub.dlg, user_data, ls);
				ls->wbut_vis = RND_DAD_CURRENT(ls->sub.dlg);
			RND_DAD_PICBUTTON(ls->sub.dlg, all_invis_xpm);
				RND_DAD_HELP(ls->sub.dlg, "all layers invisible\nexcept for the current layer group");
				RND_DAD_CHANGE_CB(ls->sub.dlg, layer_button_cb);
				RND_DAD_SET_ATTR_FIELD(ls->sub.dlg, user_data, ls);
				ls->wbut_invis = RND_DAD_CURRENT(ls->sub.dlg);
			RND_DAD_BEGIN_HBOX(ls->sub.dlg);
				RND_DAD_COMPFLAG(ls->sub.dlg, RND_HATF_EXPFILL);
			RND_DAD_END(ls->sub.dlg);
		RND_DAD_END(ls->sub.dlg);
	RND_DAD_END(ls->sub.dlg);
	RND_DAD_DEFSIZE(ls->sub.dlg, 210, 200);
	RND_DAD_MINSIZE(ls->sub.dlg, 100, 100);
}

static void layersel_update_vis(layersel_ctx_t *ls, camv_design_t *camv)
{
	long n;
	for(n = 0; n < ls->layers.used; n++)
		lys_update_vis(camv, ls->layers.array[n]);
}

static void layersel_build(void)
{
	layersel_docked_create(&layersel, &camv);
	if (rnd_hid_dock_enter(&layersel.sub, RND_HID_DOCK_LEFT, "layersel") == 0) {
		layersel.sub_inited = 1;
		layersel_update_vis(&layersel, &camv);
	}
}

void camv_layersel_gui_init_ev(rnd_design_t *hidlib, void *user_data, int argc, rnd_event_arg_t argv[])
{
	if ((RND_HAVE_GUI_ATTR_DLG) && (rnd_gui->get_menu_cfg != NULL)) {
		layersel.camv = &camv;
		layersel_build();
	}
}

void camv_layersel_vis_chg_ev(rnd_design_t *hidlib, void *user_data, int argc, rnd_event_arg_t argv[])
{
	if ((!layersel.sub_inited) || (layersel.lock_vis > 0))
		return;
	layersel_update_vis(&layersel, &camv);
}

void camv_layersel_layer_chg_ev(rnd_design_t *hidlib, void *user_data, int argc, rnd_event_arg_t argv[])
{
	if ((RND_HAVE_GUI_ATTR_DLG) && (rnd_gui->get_menu_cfg != NULL) && (layersel.sub_inited)) {
		rnd_hid_dock_leave(&layersel.sub);
		layersel.sub_inited = 0;
		layersel_build();
	}
}

/* return whether the layer at idx is a sub-layer */
static int is_sub(int idx)
{
	camv_design_t *camv = layersel.camv;
	return ((camv_layer_t *)camv->layers.array[idx])->sub;
}

/* low level move a layer from src to dst, in 'step' direction. Returns new dst. */
static int layer_move_(int src, int dst, int step)
{
	int n;
	camv_design_t *camv = layersel.camv;
	camv_layer_t *dst_ly = camv->layers.array[src];

	for(n = src; step > 0 ? (n < dst) : (n > dst); n += step)
		camv->layers.array[n] = camv->layers.array[n+step];
	camv->layers.array[dst] = dst_ly;

	return dst - step;
}

static void layer_move(int dst)
{
	int step, sn, snv, src = layersel.camv->lysel, dst_fin;
	camv_design_t *camv = layersel.camv;

	step = dst > src ? 1 : -1;

	/* we may need to move multiple sublayers; determine how many */
	for(snv = src+1; (snv > 0) && (snv < camv->layers.used) && is_sub(snv); snv++);

/*rnd_trace("dst1=%d step=%d snv=%d blocklen=%d\n", dst, step, snv, snv-src);*/

	if (step > 0) {
			if (snv-src > 1)
				dst+=(snv-src-1);
			if (dst >= camv->layers.used)
				dst = camv->layers.used-1;

		/* move dst to jump over own sub-layers when moving up */
		if (((dst+1) < camv->layers.used) && is_sub(dst+1)) {
			for(dst++; ((dst+1) < camv->layers.used) && is_sub(dst+1); dst++) ;
			if (dst >= camv->layers.used)
				return;
		}
	}
	else {
		/* move dst to jump over sub-layers of the target when moving down */
		for(; (dst > 0) && is_sub(dst); dst--) ;
	}

/*rnd_trace("dst2=%d\n", dst);*/


	if (step > 0) {
		for(sn = snv-1; sn >= src; sn--) {
/*rnd_trace("move+ %d to %d\n", sn, dst);*/
			dst = layer_move_(sn, dst, step);
		}
		dst_fin = dst + step;
	}
	else {
		dst_fin = dst;
		for(sn = src; sn < snv; sn++) {
/*rnd_trace("move- %d to %d\n", sn, dst);*/
			dst = layer_move_(sn, dst, step);
		}
	}

	rnd_event(&layersel.camv->hidlib, CAMV_EVENT_LAYERS_CHANGED, NULL);
	layer_select(layersel.layers.array[dst_fin]);
	camv_hid_redraw(camv);
}

static void layer_del(void)
{
	int src = layersel.camv->lysel;
	camv_layer_t *ly = layersel.camv->layers.array[src];

	vtp0_remove(&layersel.camv->layers, src, 1);
	vtp0_remove(&layersel.layers, src, 1);
	camv_layer_destroy(ly);

	camv_layer_select(layersel.camv, -1);
	rnd_event(&layersel.camv->hidlib, CAMV_EVENT_LAYERS_CHANGED, NULL);
	camv_hid_redraw(layersel.camv);
}

static void layer_set_color(const rnd_color_t clr)
{
	int src = layersel.camv->lysel;
	camv_layer_t *ly = layersel.camv->layers.array[src];

	ly->color = clr;
	rnd_event(&layersel.camv->hidlib, CAMV_EVENT_LAYERS_CHANGED, NULL);
}

static void layer_rename(const char *name)
{
	int src = layersel.camv->lysel;
	camv_layer_t *ly = layersel.camv->layers.array[src];

	free(ly->short_name);
	ly->short_name = rnd_strdup(name);
	rnd_event(&layersel.camv->hidlib, CAMV_EVENT_LAYERS_CHANGED, NULL);
}

static void layer_del_all(void)
{
	long n;
	for(n = layersel.camv->layers.used-1; n >= 0; n--) {
		camv_layer_t *ly = layersel.camv->layers.array[n];

		vtp0_remove(&layersel.camv->layers, n, 1);
		vtp0_remove(&layersel.layers, n, 1);
		camv_layer_destroy(ly);
	}

	camv_layer_select(layersel.camv, -1);
	rnd_event(&layersel.camv->hidlib, CAMV_EVENT_LAYERS_CHANGED, NULL);

	camv_hid_redraw(layersel.camv);
}

static void all_vis(camv_design_t *camv, int vis)
{
	long lid;

	for(lid = 0; lid < camv->layers.used; lid++)
		camv_layer_set_vis(camv, lid, vis, 1);

	rnd_event(&layersel.camv->hidlib, CAMV_EVENT_LAYERS_CHANGED, NULL);
	camv_hid_redraw(layersel.camv);
}

/* DOC: layer.html */
const char camv_acts_Layer[] = "Layer([up|down|top|bottom|del|getidx|getlen|setcolor|rename])";
const char camv_acth_Layer[] = "Move or remove the current layer or return index\n";
fgw_error_t camv_act_Layer(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	const char *cmd = NULL, *clrstr, *name;
	camv_design_t *camv = layersel.camv;

	if ((argc != 2) && (argc != 3))
		RND_ACT_FAIL(Layer);

	RND_ACT_CONVARG(1, FGW_STR, Layer, cmd = argv[1].val.str);

	if (strcmp(cmd, "down") == 0) {
		if (camv->lysel < 0) goto nolayer;
		if (camv->lysel == 0) goto nop;
		layer_move(camv->lysel-1);
	}
	else if (strcmp(cmd, "up") == 0) {
		if (camv->lysel < 0) goto nolayer;
		if (camv->lysel == camv->layers.used-1) goto nop;
		layer_move(camv->lysel+1);
	}
	else if (strcmp(cmd, "bottom") == 0) {
		if (camv->lysel < 0) goto nolayer;
		if (camv->lysel == 0) goto nop;
		layer_move(0);
	}
	else if (strcmp(cmd, "top") == 0) {
		if (camv->lysel < 0) goto nolayer;
		if (camv->lysel == camv->layers.used-1) goto nop;
		layer_move(camv->layers.used-1);
	}
	else if (strcmp(cmd, "del") == 0) {
		if (camv->lysel < 0) goto nolayer;
		layer_del();
	}
	else if (strcmp(cmd, "all-visible") == 0) {
		all_vis(camv, 1);
	}
	else if (strcmp(cmd, "all-invisible") == 0) {
		all_vis(camv, 0);
	}
	else if (rnd_strcasecmp(cmd, "delall") == 0) {
		if (camv->lysel < 0) goto nolayer;
		layer_del_all();
	}
	else if (rnd_strcasecmp(cmd, "getidx") == 0) {
		RND_ACT_IRES(camv->lysel);
		return 0;
	}
	else if (rnd_strcasecmp(cmd, "select") == 0) {
		int idx;
		RND_ACT_CONVARG(2, FGW_INT, Layer, idx = argv[2].val.nat_int);
		layer_select_idx(&layersel, idx);
		return 0;
	}
	else if (rnd_strcasecmp(cmd, "getlen") == 0) {
		RND_ACT_IRES(camv->layers.used);
		return 0;
	}
	else if (rnd_strcasecmp(cmd, "setcolor") == 0) {
		rnd_color_t clr;

		RND_ACT_CONVARG(2, FGW_STR, Layer, clrstr = argv[2].val.str);
		if (rnd_color_load_str(&clr, clrstr) != 0) {
			rnd_message(RND_MSG_ERROR, "Invalid color in Layer(setcolor): '%s'\b", clrstr);
			RND_ACT_IRES(-1);
			return 0;
		}

		layer_set_color(clr);
	}
	else if (rnd_strcasecmp(cmd, "rename") == 0) {
		RND_ACT_CONVARG(2, FGW_STR, Layer, name = argv[2].val.str);
		layer_rename(name);
	}

	else {
		RND_ACT_FAIL(Layer);
	}

	nop:;
	RND_ACT_IRES(0);
	return 0;

	nolayer:;
	rnd_message(RND_MSG_ERROR, "no layer selected");
	RND_ACT_IRES(-1);
	return 0;
}
