/**
 ** PerfDaemon.java - unprivileged client for the elphel-perfd performance-
 ** management daemon (CPU + GPU clock holds without sudo; see perfd/README.md).
 **
 ** Design ruled by Andrey 07/17/2026: the daemon ships with the imagej-elphel
 ** codebase (the capability follows the hardware the project runs on), Eyesis
 ** checks for it at startup - uses it when installed, suggests the one-time
 ** "sudo .../perfd/install.sh" when not.
 **
 ** Copyright (C) 2026 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  PerfDaemon.java 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 3 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, see <http://www.gnu.org/licenses/>.
 ** -----------------------------------------------------------------------------**
 */
package com.elphel.imagej.common;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * Plain-TCP (127.0.0.1:48890, Java-8 compatible, zero dependencies) client
 * for elphel-perfd. A hold is tied to the open socket: closing it (or the
 * JVM exiting for any reason) releases the hold in the daemon - crash-safe
 * by construction. By Claude on 07/17/2026.
 */
public class PerfDaemon {
	public static final String HOST = "127.0.0.1";
	public static final int    PORT = Integer.parseInt(
			System.getProperty("elphel.perfd.port", "48890"));
	private static final int   CONNECT_TIMEOUT_MS = 300;
	private static boolean     startup_reported = false;

	/** A live profile hold; close() (or JVM exit) releases it. */
	public static class Hold implements AutoCloseable {
		private final Socket socket;
		private final Writer out;
		private final String profile;
		private Hold(Socket socket, Writer out, String profile) {
			this.socket = socket;
			this.out = out;
			this.profile = profile;
		}
		public String getProfile() { return profile; }
		@Override public void close() {
			try {
				out.write("RELEASE\n");
				out.flush();
			} catch (IOException e) { /* daemon releases on disconnect anyway */ }
			try { socket.close(); } catch (IOException e) { }
		}
	}

	private static Socket connect() throws IOException {
		final Socket s = new Socket();
		s.connect(new InetSocketAddress(HOST, PORT), CONNECT_TIMEOUT_MS);
		s.setSoTimeout(2000);
		return s;
	}

	/** One-shot command on a throwaway connection; null if the daemon is absent. */
	private static String query(String command) {
		try (Socket s = connect()) {
			final Writer w = new OutputStreamWriter(s.getOutputStream(), StandardCharsets.UTF_8);
			final BufferedReader r = new BufferedReader(
					new InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8));
			w.write(command + "\n");
			w.flush();
			return r.readLine();
		} catch (IOException e) {
			return null;
		}
	}

	/** true when elphel-perfd is installed and answering. */
	public static boolean isAvailable() {
		final String reply = query("PING");
		return (reply != null) && reply.startsWith("OK");
	}

	/** Daemon status line (JSON after "OK "), or null when absent. */
	public static String status() {
		return query("STATUS");
	}

	/**
	 * Acquire a profile hold ("measure" = deterministic locked clocks,
	 * "max" = production performance). Returns null (with one console line,
	 * never an exception) when the daemon is not installed/reachable -
	 * callers proceed normally without it.
	 */
	public static Hold hold(String profile) {
		try {
			final Socket s = connect();
			final Writer w = new OutputStreamWriter(s.getOutputStream(), StandardCharsets.UTF_8);
			final BufferedReader r = new BufferedReader(
					new InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8));
			w.write("HOLD " + profile + "\n");
			w.flush();
			final String reply = r.readLine();
			if ((reply != null) && reply.startsWith("OK")) {
				System.out.println("PerfDaemon: " + reply);
				return new Hold(s, w, profile);
			}
			System.out.println("PerfDaemon: hold '" + profile + "' refused: " + reply);
			s.close();
			return null;
		} catch (IOException e) {
			System.out.println("PerfDaemon: not available (" + e.getMessage() +
					") - continuing with default power management");
			return null;
		}
	}

	/**
	 * Startup probe (once per program): one console line either way; when the
	 * daemon is absent, prints the one-time install command and continues.
	 */
	public static void startupCheck() {
		if (startup_reported) return;
		startup_reported = true;
		if (isAvailable()) {
			System.out.println("PerfDaemon: elphel-perfd available on " + HOST + ":" + PORT +
					" (CPU+GPU clock holds without sudo; profiles: measure/max)");
		} else {
			System.out.println("PerfDaemon: elphel-perfd NOT installed - runs use default power "+
					"management (timing varies with clock boost). Install once with:");
			System.out.println("PerfDaemon:     sudo <imagej-elphel>/perfd/install.sh");
		}
	}
}
