package com.qsjnic.mcu.domain;

import java.nio.charset.StandardCharsets;

/**
 * Pure-function command encoders. Each method returns the payload byte[]
 * that must be sent with the corresponding msgType + cmd.
 *
 * <p>These are extracted 1:1 from the inline {@code new byte[]{...}}
 * expressions previously scattered across {@code QsMcuSystemService}.
 */
public final class Commands {

    public static final class Public {

        public static byte[] reqInit() {
            return EMPTY;
        }

        public static byte[] reqMute(boolean on) {
            return new byte[] { (byte) (on ? 1 : 0) };
        }

        public static byte[] timeInfo(int year, int month, int day,
                                      int hour, int minute, int second) {
            require(year >= 2000 && year <= 2099, "year out of range");
            require(month >= 1 && month <= 12, "month out of range");
            require(day >= 1 && day <= 31, "day out of range");
            require(hour >= 0 && hour <= 23, "hour out of range");
            require(minute >= 0 && minute <= 59, "minute out of range");
            require(second >= 0 && second <= 59, "second out of range");
            return new byte[] {
                    (byte) (year - 2000), (byte) month, (byte) day,
                    (byte) hour, (byte) minute, (byte) second
            };
        }

        public static byte[] tftDuty(int duty1, int duty2) {
            require(duty1 >= 0 && duty1 <= 100, "duty1 out of range");
            require(duty2 >= 0 && duty2 <= 100, "duty2 out of range");
            return new byte[] { (byte) duty1, (byte) duty2 };
        }

        public static byte[] reqSleep() {
            return EMPTY;
        }

        public static byte[] reqTypeInfo(int type) {
            require(type >= 0 && type <= 0xFF, "type out of range");
            return new byte[] { (byte) type };
        }

        public static byte[] reqArmReset() {
            return EMPTY;
        }

        public static byte[] tftOnOff(boolean on) {
            return new byte[] { (byte) (on ? 1 : 0) };
        }

        public static byte[] factoryRestore() {
            return EMPTY;
        }

        public static byte[] ampPower(boolean on) {
            return new byte[] { (byte) (on ? 1 : 0) };
        }

        public static byte[] keyBacklight(int dutyPercent) {
            require(dutyPercent >= 0 && dutyPercent <= 100, "duty out of range");
            return new byte[] { (byte) dutyPercent };
        }

        public static byte[] subMute(boolean on) {
            return new byte[] { (byte) (on ? 1 : 0) };
        }

        public static byte[] keyPwrStatus(int status) {
            require(status >= 0 && status <= 0xFF, "status out of range");
            return new byte[] { (byte) status };
        }

        public static byte[] reverse(boolean on) {
            return new byte[] { (byte) (on ? 1 : 0) };
        }

        public static byte[] speedRatio(int ratio) {
            require(ratio >= 0 && ratio <= 0xFF, "ratio out of range");
            return new byte[] { (byte) ratio };
        }

        public static byte[] reqArmForceUpgrade() {
            return EMPTY;
        }

        public static byte[] reqTripClear() {
            return reqTripClear(1);
        }

        public static byte[] reqTripClear(int type) {
            require(type >= 1 && type <= 3, "type out of range");
            return new byte[] { (byte) type };
        }

        public static byte[] switchStatus(int switchBitmap) {
            require(switchBitmap >= 0 && switchBitmap <= 0xFF, "bitmap out of range");
            return new byte[] { (byte) (switchBitmap & 0xFF) };
        }

        public static byte[] btErrorCode(int code) {
            require(code >= 0 && code <= 0xFF, "code out of range");
            return new byte[] { (byte) code };
        }

        public static byte[] gpsInfo(int latE6, int lonE6, int speedKmh, int heading) {
            int lonStatus = (lonE6 < 0 ? 0x02 : 0x00) | (lonE6 == 0 && latE6 == 0 ? 0x00 : 0x01);
            int latStatus = (latE6 < 0 ? 0x02 : 0x00) | (lonE6 == 0 && latE6 == 0 ? 0x00 : 0x01);
            return gpsInfoFull(0, Math.abs(lonE6), lonStatus, Math.abs(latE6), latStatus);
        }

        public static byte[] gpsInfoFull(int moduleStatus, int lonPpm, int lonStatus,
                                         int latPpm, int latStatus) {
            require(moduleStatus >= 0 && moduleStatus <= 0xFF, "moduleStatus out of range");
            require(lonPpm >= 0, "lonPpm must be non-negative");
            require(lonStatus >= 0 && lonStatus <= 0xFF, "lonStatus out of range");
            require(latPpm >= 0, "latPpm must be non-negative");
            require(latStatus >= 0 && latStatus <= 0xFF, "latStatus out of range");
            return new byte[] {
                    (byte) moduleStatus,
                    (byte) ((lonPpm >> 24) & 0xFF), (byte) ((lonPpm >> 16) & 0xFF),
                    (byte) ((lonPpm >> 8) & 0xFF),  (byte) (lonPpm & 0xFF),
                    (byte) lonStatus,
                    (byte) ((latPpm >> 24) & 0xFF), (byte) ((latPpm >> 16) & 0xFF),
                    (byte) ((latPpm >> 8) & 0xFF),  (byte) (latPpm & 0xFF),
                    (byte) latStatus
            };
        }

        public static byte[] totalMiles(int milesTenthKm) {
            require(milesTenthKm >= 0, "miles must be non-negative");
            return new byte[] {
                    (byte) ((milesTenthKm >> 24) & 0xFF), (byte) ((milesTenthKm >> 16) & 0xFF),
                    (byte) ((milesTenthKm >> 8) & 0xFF),  (byte) (milesTenthKm & 0xFF)
            };
        }

        public static byte[] workMode(int mode) {
            require(mode >= 0 && mode <= 0xFF, "mode out of range");
            return new byte[] { (byte) mode };
        }

        public static byte[] fuelCal(byte[] data) {
            require(data == null || data.length == 2, "fuelCal payload must be 2 bytes");
            return data == null ? EMPTY : data;
        }

        public static byte[] vehicleCfg(byte[] data) {
            require(data == null || data.length == 16, "vehicleCfg payload must be 16 bytes");
            return data == null ? EMPTY : data;
        }

        public static byte[] reqVehicleCfg() {
            return EMPTY;
        }

        public static byte[] armSwVersion(String version) {
            require(version != null, "version is null");
            return versionPayload(version);
        }

        public static byte[] armHwVersion(String version) {
            require(version != null, "version is null");
            return versionPayload(version);
        }

        public static byte[] autoClearMeterType(int type) {
            require(type >= 0 && type <= 0xFF, "type out of range");
            return new byte[] { (byte) type };
        }

        public static byte[] responseFactoryCmd(byte[] data) {
            return data == null ? EMPTY : data;
        }

        public static byte[] responseTestCmd(byte[] data) {
            return data == null ? EMPTY : data;
        }

        private Public() {}
    }

    public static final class Radio {

        public static byte[] setBand(int band) {
            return setBand(band, 0);
        }

        public static byte[] setBand(int band, int freq) {
            require(band >= 0 && band <= 1, "band out of range");
            require(freq >= 0 && freq <= 0xFFFF, "freq out of range");
            return new byte[] { (byte) band, (byte) ((freq >> 8) & 0xFF), (byte) (freq & 0xFF) };
        }

        public static byte[] setFrequency(int freq) {
            require(freq >= 0 && freq <= 0xFFFF, "freq out of range");
            return new byte[] {
                    (byte) ((freq >> 8) & 0xFF), (byte) (freq & 0xFF)
            };
        }

        public static byte[] setFreqRange(int area, int amMin, int amMax,
                                          int fmMin, int fmMax, int amStep, int fmStep) {
            return new byte[] {
                    (byte) area,
                    (byte) ((amMin >> 8) & 0xFF), (byte) (amMin & 0xFF),
                    (byte) ((amMax >> 8) & 0xFF), (byte) (amMax & 0xFF),
                    (byte) ((fmMin >> 8) & 0xFF), (byte) (fmMin & 0xFF),
                    (byte) ((fmMax >> 8) & 0xFF), (byte) (fmMax & 0xFF),
                    (byte) amStep, (byte) fmStep
            };
        }

        public static byte[] setSeek(int mode, int pty) {
            return new byte[] { (byte) mode, (byte) pty };
        }

        public static byte[] setSwitch(int subType, int value) {
            return new byte[] { (byte) subType, (byte) value };
        }

        public static byte[] antPower(boolean on) {
            return new byte[] { (byte) (on ? 1 : 0) };
        }

        public static byte[] regMute(boolean mute) {
            return new byte[] { (byte) (mute ? 1 : 0) };
        }

        private Radio() {}
    }

    public static final class Key {

        public static byte[] key(int keyValue, int keyStatus) {
            require(keyValue >= 0 && keyValue <= 0x0B, "keyValue out of range");
            require(keyStatus >= 0 && keyStatus <= 0x04, "keyStatus out of range");
            return new byte[] { (byte) keyValue, (byte) keyStatus };
        }

        private Key() {}
    }

    public static final class Update {

        public static byte[] bootBegin() {
            return EMPTY;
        }

        public static byte[] bootBegin(long binSize, String projectId) {
            require(binSize >= 0 && binSize <= 0xFFFFFFFFL, "binSize out of range");
            byte[] project = projectBytes(projectId);
            byte[] payload = new byte[4 + project.length];
            payload[0] = (byte) ((binSize >> 24) & 0xFF);
            payload[1] = (byte) ((binSize >> 16) & 0xFF);
            payload[2] = (byte) ((binSize >> 8) & 0xFF);
            payload[3] = (byte) (binSize & 0xFF);
            System.arraycopy(project, 0, payload, 4, project.length);
            return payload;
        }

        public static byte[] bootData(byte[] chunk, int index) {
            require(chunk != null, "chunk is null");
            byte[] payload = new byte[2 + chunk.length];
            payload[0] = (byte) ((index >> 8) & 0xFF);
            payload[1] = (byte) (index & 0xFF);
            System.arraycopy(chunk, 0, payload, 2, chunk.length);
            return payload;
        }

        public static byte[] bootChecksum(int checksum) {
            return new byte[] {
                    (byte) ((checksum >> 24) & 0xFF), (byte) ((checksum >> 16) & 0xFF),
                    (byte) ((checksum >> 8) & 0xFF),  (byte) (checksum & 0xFF)
            };
        }

        public static byte[] bootChecksumMd5(byte[] md5) {
            require(md5 != null && md5.length == 16, "md5 must be 16 bytes");
            byte[] payload = new byte[16];
            System.arraycopy(md5, 0, payload, 0, 16);
            return payload;
        }

        private Update() {}
    }

    private static final byte[] EMPTY = new byte[0];

    private static void require(boolean cond, String msg) {
        if (!cond) throw new IllegalArgumentException(msg);
    }

    private static byte[] versionPayload(String version) {
        byte[] text = version.getBytes(StandardCharsets.UTF_8);
        require(text.length <= 0xFF, "version too long");
        byte[] payload = new byte[1 + text.length];
        payload[0] = (byte) text.length;
        System.arraycopy(text, 0, payload, 1, text.length);
        return payload;
    }

    private static byte[] projectBytes(String projectId) {
        String project = projectId == null ? "" : projectId;
        require(project.length() <= 5, "projectId too long");
        return project.getBytes(StandardCharsets.US_ASCII);
    }

    private Commands() {}
}
