/*
 * Copyright 2021, 2022 Collabora, Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <assert.h>
#include <thread>

#include <unistd.h>
#include <wayland-client.h>
#include <wayland-util.h>

#ifndef ARRAY_LENGTH
#define ARRAY_LENGTH(array) (sizeof(array) / sizeof(array[0]))
#endif

#include "AglShellGrpcClient.h"

enum action_state {
	NONE 		= -1,
	ACTIVATE,
	DEACTIVATE,
	SET_FLOAT,
	SET_SPLIT,
	SET_NORMAL,
	GETOUTPUTS,
	SET_FULLSCREEN,
	SET_APP_ON_OUTPUT,
	SET_APP_POS,
	SET_APP_SCALE,
	SET_APP_SPLIT,
};

static struct action {
	enum action_state action;
	const char *name;
} actions[] = {
	{ ACTIVATE,		"ACTIVATE" 	},
	{ DEACTIVATE,		"DEACTIVATE" 	},
	{ GETOUTPUTS,		"GETOUTPUTS" 	},
	{ SET_FLOAT,		"FLOAT" 	},
	{ SET_NORMAL,		"NORMAL" 	},
	{ SET_FULLSCREEN,	"FULLSCREEN" 	},
	{ SET_APP_ON_OUTPUT,	"ON_OUTPUT" 	},
	{ SET_APP_POS,		"POSITION" 	},
	{ SET_APP_SCALE,	"SCALE" 	},
	{ SET_APP_SPLIT,	"SPLIT" 	},
};

static int
get_action(const char *action_name)
{
	size_t i;

	for (i = 0; i < ARRAY_LENGTH(actions); i++) {
		if (action_name && strcasecmp(action_name, actions[i].name) == 0)
			return actions[i].action;
	}

	return -1;
}

static void
run_in_thread(GrpcClient *client)
{
	grpc::Status status = client->Wait();
}

static void
help(char **argv)
{
	fprintf(stderr, "Usage: %s [-a action] [-p app_id] [-o output_name] [-l] [-r] [-s]\n",
			argv[0]);
	fprintf(stderr, "\t-a -- action activate|deactivate|float|normal|getoutputs|fullscreen|on_output|position|scale|split\n");
	fprintf(stderr, "\t-p -- app_id application_id\n");
	fprintf(stderr, "\t-o -- output_name one of the outputs from getoutputs action\n");
	fprintf(stderr, "\t-l -- continuously listen for window state events\n");
	fprintf(stderr, "\t-r -- orientation for split\n");
	fprintf(stderr, "\t-w -- width of the window (if not specified defaults t0) for split\n");
	fprintf(stderr, "\t-s -- sticky window for split\n");
	exit(EXIT_FAILURE);
}

static void
app_status_callback(::agl_shell_ipc::AppStateResponse app_response)
{
	std::cout << " >> AppStateResponse app_id " <<
		app_response.app_id() << ", with state " <<
		app_response.state() << std::endl;
}

static void
read_outputs(GrpcClient *client)
{
	std::vector<std::string> outputs = client->GetOutputs();

	if (outputs.empty())
		return;

	for (size_t i = 0; i < outputs.size(); i++)
		fprintf(stderr, " %s ", outputs[i].c_str());

	fprintf(stderr, "\n");
}

static uint32_t orientation_trans(char *orientation)
{
	if (strcmp(orientation, "left") == 0)
		return 1;
	else if (strcmp(orientation, "right") == 0)
		return 2;
	else if (strcmp(orientation, "top") == 0)
		return 3;
	else if (strcmp(orientation, "bottom") == 0)
		return 4;

	return 0;
}

int main(int argc, char *argv[])
{
	char *output = NULL;
	char *action = NULL;
	char *app_id = NULL;
	char *orientation = NULL;
	// none, by default
	uint32_t orientation_translate = 0;
	int opt;
	bool listen_flag = false;
	int width = 0;
	int32_t sticky = 0;
	std::thread th;

	// app_id, output p[0] -> name, p[1] action, p[2] app_id, p[3] -> output
	while ((opt = getopt(argc, argv, "a:p:o:r:lshw:")) != -1) {
		switch (opt) {
		case 'p':
			app_id = optarg;
			break;
		case 'a':
			action = optarg;
			break;
		case 'o':
			output = optarg;
			break;
		case 'r':
			orientation = optarg;
			break;
		case 'l':
			listen_flag = true;
			break;
		case 's':
			sticky = 1;
			break;
		case 'w':
			width = strtoul(optarg, NULL, 10);
			break;
		case 'h':
		default: /* '?' */
			help(argv);
		}
	}

	if (!action && !listen_flag) {
		help(argv);
		exit(EXIT_FAILURE);
	}

	// start grpc connection
	GrpcClient *client = new GrpcClient();

	if (listen_flag) {
		fprintf(stderr, "Listening for events...\n");
		th = std::thread(run_in_thread, client);
		client->AppStatusState(app_status_callback);
	}

	switch (get_action(action)) {
	case ACTIVATE:
		if (!output && !app_id) {
			fprintf(stderr, "Activation require both an app_id and an output\n");
			help(argv);
		}
		fprintf(stderr, "Activating application '%s' on output '%s'\n",
				app_id, output);
		client->ActivateApp(std::string(app_id), std::string(output));
		break;
	case DEACTIVATE:
		if (!app_id) {
			fprintf(stderr, "Deactivation require an app_id\n");
			help(argv);
		}
		fprintf(stderr, "Deactivating application '%s'\n", app_id);
		client->DeactivateApp(std::string(app_id));
		break;
	case SET_FLOAT:
		if (!app_id) {
			fprintf(stderr, "Float require an app_id\n");
			help(argv);
		}
		fprintf(stderr, "Floating application '%s'\n", app_id);
		client->SetAppFloat(std::string(app_id), 40, 300);
		break;
	case SET_FULLSCREEN:
		if (!app_id) {
			fprintf(stderr, "Fullscreen require an app_id\n");
			help(argv);
		}
		fprintf(stderr, "Fullscreened application '%s'\n", app_id);
		client->SetAppFullscreen(std::string(app_id));
		break;
	case SET_APP_ON_OUTPUT:
		if (!app_id) {
			fprintf(stderr, "AppOnOutput require an app_id\n");
			help(argv);
		}
		if (!output) {
			fprintf(stderr, "AppOnOutput require an output\n");
			help(argv);
		}
		fprintf(stderr, "Putting application '%s' on output '%s'\n", app_id, output);
		client->SetAppOnOutput(std::string(app_id), std::string(output));
		break;
	case SET_NORMAL:
		if (!app_id) {
			fprintf(stderr, "Normal/maximized require an app_id\n");
			help(argv);
		}
		fprintf(stderr, "Maximizing application '%s'\n", app_id);
		client->SetAppNormal(std::string(app_id));
		break;
	case GETOUTPUTS:
		read_outputs(client);
		break;
	case SET_APP_POS:
		if (!app_id) {
			fprintf(stderr, "Positioning require an app_id\n");
			help(argv);
		}
		fprintf(stderr, "Set app position for  application '%s'\n", app_id);
		client->SetAppPosition(std::string(app_id), 550, 550);
		break;
	case SET_APP_SCALE:
		if (!app_id) {
			fprintf(stderr, "Scale require an app_id\n");
			help(argv);
		}
		fprintf(stderr, "Set scale for  application '%s'\n", app_id);
		client->SetAppScale(std::string(app_id), 200, 200);
		break;
	case SET_APP_SPLIT:
		if (!orientation) {
			fprintf(stderr, "split require orientation\n");
			help(argv);
		}

		orientation_translate = orientation_trans(orientation);

		if (!app_id) {
			fprintf(stderr, "Split require an app_id\n");
			help(argv);
		}

		if (!output) {
			fprintf(stderr, "split require an output\n");
			help(argv);
		}


		fprintf(stderr, "Set split orientation '%s' for application '%s' on output '%s'\n", 
				orientation, app_id, output);
		client->SetAppSplit(std::string(app_id),
				orientation_translate, width, sticky, std::string(output));
		break;
	default:
		// allow listen flag to be passed
		if (listen_flag)
			break;

		fprintf(stderr, "Unknown action passed. Possible actions:\n\t");
		for (size_t i = 0; i < ARRAY_LENGTH(actions); i++)
			fprintf(stderr, " %s ", actions[i].name);

		help(argv);
		break;
	}

	if (listen_flag) {
		th.join();
	}

	return 0;
}
