/*
 * sai-builder - src/builder/b-power.c
 *
 * Copyright (C) 2019 - 2025 Andy Green <andy@warmcat.com>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation:
 *  version 2.1 of the License.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *  MA  02110-1301  USA
 */

#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
#include <limits.h>
#include <stdlib.h>

#include <sys/types.h>

#include "b-private.h"

extern struct lws_spawn_piped *lsp_suspender;

/*
 * This is used to check with sai-power if we should stay up (due to the power
 * being turned on manually)
 */


LWS_SS_USER_TYPEDEF
        char                    payload[200];
        size_t                  size;
        size_t                  pos;
} saib_power_stay_t;


static lws_ss_state_return_t
saib_power_stay_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
	if (len < 1)
		return 0;

	builder.stay = *buf == '1';

//	lwsl_err("%s: stay %d\n", __func__, builder.stay);

	if (builder.stay) {
		/*
		 * We need to deal with finding we have been manually powered-on.
		 * Just cancel any pending grace period
		 */
		lws_sul_cancel(&builder.sul_idle);

		// lwsl_warn("%s: %s: stay applied: cancelled idle grace time\n",
		//			__func__, builder.host);
	} else {

		/*
		 * If any plat on this builder has tasks, just
		 * leave it
		 */
		lws_start_foreach_dll_safe(struct lws_dll2 *, mp, mp1,
					builder.sai_plat_owner.head) {
			struct sai_plat *sp = lws_container_of(mp, struct sai_plat,
					sai_plat_list);

			if (sp->nspawn_owner.count) {
				lwsl_warn("%s: cancelling idle grace time as ongoing task steps\n", __func__);
				lws_sul_cancel(&builder.sul_idle);
				return 0;
			}

		} lws_end_foreach_dll_safe(mp, mp1);

		/*
		* if no ongoing tasks, and we want to go OFF, then start
		* the idle grace timer.  This will get cancelled if
		* we start a task during the grace time, otherwise it will
		* expire and do the power-off or suspend
		*/

		if (lws_dll2_is_detached(&builder.sul_idle.list)) {
			lwsl_warn("%s: %s: no stay: starting idle grace time\n",
				__func__, builder.host);
			lws_sul_schedule(builder.context, 0, &builder.sul_idle,
					sul_idle_cb, SAI_IDLE_GRACE_US);
		}
	}

	return 0;
}

LWS_SS_INFO("sai_power", saib_power_stay_t)
	.rx				= saib_power_stay_rx,
};


void
sul_stay_cb(lws_sorted_usec_list_t *sul)
{
	lws_ss_state_return_t r;

	/* nothing to do if no sai-power coordinates given */
	if (!builder.url_sai_power)
		return;

	r = lws_ss_client_connect(builder.ss_stay);
	if (r)
		lwsl_ss_err(builder.ss_stay, "Unable to start stay connection (%d)", (int)r);

	lws_sul_schedule(builder.context, 0, &builder.sul_stay,
			 sul_stay_cb, SAI_STAY_POLL_US);
}

int
saib_stay_init(void)
{
	lws_ss_state_return_t r;

	/*
	 * ss used to query sai-power about stay situation
	 */

	if (lws_ss_create(builder.context, 0, &ssi_saib_power_stay_t,
			  NULL, &builder.ss_stay, NULL, NULL)) {
		lwsl_err("%s: failed to create sai-power-stay ss\n", __func__);
		return 1;
	}

	if (!builder.url_sai_power)
		return 1;

	if (!suspender_exists)
		return LWSSSSRET_OK;

	snprintf(builder.path, sizeof(builder.path) - 1, "%s/stay/%s",
		 builder.url_sai_power, builder.host);

	r = lws_ss_set_metadata(builder.ss_stay, "url", builder.path, strlen(builder.path));
	if (r)
		lwsl_err("%s: set_metadata said %d\n", __func__, (int)r);

	lws_sul_schedule(builder.context, 0, &builder.sul_stay,
			 sul_stay_cb, 1000);

	return 0;
}


/*
 * This is used to fire http request to sai-power for power-down
 */

LWS_SS_USER_TYPEDEF
        char                    payload[200];
        size_t                  size;
        size_t                  pos;
} saib_power_link_t;

static lws_ss_state_return_t
saib_power_link_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
#if !defined(WIN32)
	int fd = saib_suspender_get_pipe();
	uint8_t te = 0;
	ssize_t n;

	if (len < 4 || !(flags & LWSSS_FLAG_SOM))
		return 0;

	if (memcmp(buf, "ACK:", 4)) {
		lwsl_warn("%s: sai-power didn't start power-off: %.*s\n",
				__func__, (int)len, (const char *)buf);
		return LWSSSSRET_OK;
	}

	if (!suspender_exists)
		return LWSSSSRET_OK;

	lwsl_notice("%s: sai-power scheduling power-off: doing shutdown...\n", __func__);

	/*
	 * In the grace time for actioning the power-off, we should shutdown
	 * cleanly
	 */

	n = write(fd, &te, 1);
	if (n != 1)
		lwsl_err("%s: unable to request shutdown\n", __func__);
#endif
	return LWSSSSRET_OK;
}

LWS_SS_INFO("sai_power", saib_power_link_t)
	.rx				= saib_power_link_rx,
};


static void
sul_do_suspend_cb(lws_sorted_usec_list_t *sul)
{
#if !defined(WIN32)
	int fd = saib_suspender_get_pipe();
	uint8_t te = 1;
	ssize_t n;

	lwsl_notice("%s: actioning suspend...\n", __func__);

	n = write(fd, &te, 1);
	if (n == 1) {
#if defined(WIN32)
		Sleep(2000);
#else
		sleep(2);
#endif
		/*
		 * There were 0 tasks ongoing for us to suspend, start off
		 * with the same assumption and set the idle grace time
		 */
		lws_sul_schedule(builder.context, 0, &builder.sul_idle,
				 sul_idle_cb, SAI_IDLE_GRACE_US);
		lwsl_notice("%s: resuming after suspend\n", __func__);
	} else
		lwsl_err("%s: failed to request suspend\n", __func__);
#endif
}

/*
 * The grace time is up, ask for the suspend
 */

void
sul_idle_cb(lws_sorted_usec_list_t *sul)
{
	lws_ss_state_return_t r;
	char path[256];

#if !defined(WIN32)
	if (builder.stay)
		return;

	lwsl_notice("%s: idle period ended...\n", __func__);

	if (builder.power_off_type &&
	    !strcmp(builder.power_off_type, "suspend")) {

		lwsl_notice("%s: starting suspend...\n", __func__);

		/*
		 * Let everybody know we are trying to power down
		 */
		lws_start_foreach_dll(struct lws_dll2 *, d,
				      builder.sai_plat_owner.head) {
			sai_plat_t *p = lws_container_of(d, sai_plat_t,
							 sai_plat_list);
			p->powering_down = 1;
		} lws_end_foreach_dll(d);

		lws_start_foreach_dll(struct lws_dll2 *, d,
				      builder.sai_plat_server_owner.head) {
			struct sai_plat_server *spm = lws_container_of(d,
						struct sai_plat_server, list);

			if (saib_srv_queue_json_fragments_helper(spm->ss,
					lsm_schema_map_plat,
					LWS_ARRAY_SIZE(lsm_schema_map_plat),
					&builder.sai_plat_owner))
				return;

		} lws_end_foreach_dll(d);

		/*
		 * give the event loop a moment to send the notifications out
		 * before we do the blocking suspend part
		 */
		lws_sul_schedule(builder.context, 0, &builder.sul_do_suspend,
				 sul_do_suspend_cb, 2 * LWS_US_PER_SEC);

		return;
	}
#endif

	if (!builder.url_sai_power)
		return;

	snprintf(path, sizeof(path) - 1, "%s/auto-power-off/%s",
		 builder.url_sai_power, builder.host);

	lwsl_notice("%s: setting url metadata %s\n", __func__, path);

	r = lws_ss_set_metadata(builder.ss_power_off, "url", path, strlen(path));
	if (r)
		lwsl_err("%s: set_metadata said %d\n", __func__, (int)r);

	lws_ss_start_timeout(builder.ss_power_off, 3000); /* 3 sec */

	if (lws_ss_request_tx(builder.ss_power_off))
		lwsl_ss_warn(builder.ss_power_off, "Unable to request tx");
}

int
saib_power_init(void)
{
	/*
	 * Do we have a url for sai-power?  If not, nothing we can do.
	 */

	if (!builder.url_sai_power)
		return 1;

	/*
	 * The plan is ask sai-power to turn us off... we don't know our
	 * dependency situation since we're just a standalone builder.
	 *
	 * We will have to let sai-power figure the deps out and say if
	 * it's willing to auto power-off or not.
	 */

	lwsl_notice("%s: creating sai-power ss...\n", __func__);

	if (lws_ss_create(builder.context, 0, &ssi_saib_power_link_t,
			  (void *)builder.host, &builder.ss_power_off, NULL, NULL)) {
		lwsl_err("%s: failed to create sai-power ss\n", __func__);
		return 1;
	}

	return 0;
}

#if defined(__APPLE__)
void
sul_release_wakelock_cb(lws_sorted_usec_list_t *sul)
{
	lwsl_notice("%s: releasing wakelock (pid %d)\n", __func__, (int)builder.wakelock_pid);
	if (builder.wakelock_pid) {
		kill(builder.wakelock_pid, SIGTERM);
		waitpid(builder.wakelock_pid, NULL, 0);
		builder.wakelock_pid = 0;
	}
}
#endif