package kaka.cakelight;

import kaka.cakelight.mode.*;

import java.util.function.BiFunction;
import java.util.stream.Stream;

class Commands {
    private static Console.Command command(String[] names, BiFunction<Console, String[], Boolean> activate) {
        return new Console.Command() {
	    @Override
	    public String[] getNames() {
		return names;
	    }

	    @Override
	    public Object activate(Console console, String[] args) {
		if (!activate.apply(console, args)) {
		    console.out("did NOT run command");
		}
		return null;
	    }
	};
    }

    private static Console.Command modeCommand(String[] names, BiFunction<Console, String[], Mode> activate) {
        return new Console.Command() {
	    @Override
	    public String[] getNames() {
		return names;
	    }

	    @Override
	    public Object activate(Console console, String[] args) {
		Mode mode = activate.apply(console, args);
		if (mode == null) {
		    console.out("did NOT run command");
		}
		return mode;
	    }
	};
    }

    static Console.Command help() {
	return command(new String[] {"?", "h", "help"}, (console, args) -> {
	    for (Console.Command c : console.getCommands()) {
		System.out.println(String.join("|", c.getNames()));
	    }
	    return true;
	});
    }

    static Console.Command quit() {
	return command(new String[] {"q", "quit"}, (console, args) -> {
	    console.quit();
	    console.out("terminating");
	    return true;
	});
    }

    static Console.Command push() {
        return command(new String[] {"push"}, (console, args) -> {
            Object obj = console.internalHandleInput(String.join(" ", args));
	    if (obj instanceof Mode) { // obj could be anything, which should be fixed
		console.out("pushing mode " + obj.getClass().getSimpleName());
		console.getCakelight().pushMode((Mode) obj);
	    }
            return true;
	});
    }

    static Console.Command pop() {
        return command(new String[] {"pop"}, (console, args) -> {
            console.out("popping mode " + console.getCakelight().popMode().getClass().getSimpleName());
            return true;
	});
    }

    static Console.Command video() {
        return modeCommand(new String[] {"v", "video"}, (console, args) -> new VideoMode());
    }

    static Console.Command color() {
        return modeCommand(new String[] {"c", "col", "color"}, (console, args) -> {
            Color c = null;
            if (args.length == 1) {
		c = console.parseColor(args[0]);
	    } else if (args.length == 3) {
		c = Color.rgb(
			Integer.parseInt(args[0]),
			Integer.parseInt(args[1]),
			Integer.parseInt(args[2])
		);
	    }
            if (c != null) {
		console.out("setting color to " + c);
		return new SingleColorMode(c);
	    }
	    return null;
	});
    }

    static Console.Command brightness() {
	return command(new String[] {"b", "brightness"}, (console, args) -> {
	    if (args.length == 1) {
	        int b = Integer.parseInt(args[0].replaceAll("[+-]+", ""));
	        if (args[0].startsWith("+")) {
		    b = Math.min(console.getConfig().leds.brightness + b, 31);
		} else if (args[0].startsWith("-")) {
		    b = Math.max(console.getConfig().leds.brightness - b, 0);
		}
		console.getConfig().leds.brightness = b;
		console.out("setting brightness to " + b);
		return true;
	    } else {
		return false;
	    }
	});
    }

    static Console.Command gamma() {
	return command(new String[] {"g", "gamma"}, (console, args) -> {
	    if (args.length == 1) {
		double g = Double.parseDouble(args[0]);
		console.getConfig().gamma = g;
		Color.calculateGammaCorrection(g);
		console.out("setting gamma to " + g);
		return true;
	    } else {
		return false;
	    }
	});
    }

    static Console.Command saturation() {
	return command(new String[] {"s", "saturation"}, (console, args) -> {
	    if (args.length == 1) {
		double s = Double.parseDouble(args[0]);
		console.getConfig().video.saturation = s;
		console.out("setting saturation to " + s);
		return true;
	    } else {
		return false;
	    }
	});
    }

    static Console.Command ambientMode() {
        return modeCommand(new String[] {"m", "mode"}, (console, args) -> {
	    if (args.length == 1) {
		console.out("setting ambient mode to " + args[0]);
		return new AmbientMode(new String[]{args[0]});
	    }
	    return null;
	});
    }

    static Console.Command noiseMode() {
        return modeCommand(new String[] {"n", "noise"}, (console, args) -> {
	    if (args.length > 1) {
		console.out("setting multi-color noise mode");
		return new NoiseMode(Stream.of(args)
					     .map(console::parseColor)
					     .toArray(Color[]::new)
		);
	    }
	    return null;
	});
    }

    static Console.Command fireMode() {
        return modeCommand(new String[] {"f", "fire"}, (console, args) -> {
	    if (args.length > 1) {
		console.out("setting multi-color fire mode");
		return new FireMode(Stream.of(args)
			.map(console::parseColor)
			.toArray(Color[]::new)
		);
	    }
	    return null;
	});
    }

    static Console.Command sunriseMode() {
        return modeCommand(new String[] {"sunrise"}, (console, args) -> {
	    if (args.length == 1) {
		int durationSeconds = Integer.parseInt(args[0]);
		console.out("setting sunrise mode with duration " + durationSeconds);
		return new SunriseMode(durationSeconds);
	    }
	    return null;
	});
    }
}
