package invalid.lena.scrcpy;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

// Pure-java adb sync v1 SEND framing. Extracted from Server.push() so
// it can be unit-tested without an AdbStream or any android coupling.
//
// Layout on the wire (little-endian uint32 lengths):
//   "SEND" | u32(len(path,mode)) | path,mode
//   "DATA" | u32(chunk_size)     | chunk_bytes      (repeats, chunk <= 64K)
//   "DONE" | u32(mtime_seconds)
//   ----> response:
//   "OKAY" | u32(0)
//     or
//   "FAIL" | u32(msg_len) | msg
public final class Sync {

    public static final int CHUNK = 64 * 1024;

    // FAIL responses carry a short human-readable message; cap what a
    // corrupt or hostile length field can make us allocate.
    private static final int MAX_FAIL_MSG = 4 * 1024;

    private Sync() {}

    // Push `src` to `remotePath` with the given mode and mtime, using the
    // already-open `out` and `in` of an adb sync stream. Returns the
    // total payload byte count.
    public static long push(InputStream src, OutputStream out, InputStream in,
                            String remotePath, int mode, int mtimeSec)
            throws IOException {
        String header = remotePath + "," + mode;
        byte[] hb = header.getBytes(StandardCharsets.UTF_8);

        byte[] tag = new byte[8];
        putTag(tag, 0, "SEND");
        Wire.writeLe32(tag, 4, hb.length);
        out.write(tag);
        out.write(hb);

        byte[] chunk = new byte[CHUNK];
        byte[] dataHdr = new byte[8];
        putTag(dataHdr, 0, "DATA");
        long total = 0;
        int n;
        while ((n = src.read(chunk)) > 0) {
            Wire.writeLe32(dataHdr, 4, n);
            out.write(dataHdr);
            out.write(chunk, 0, n);
            total += n;
        }

        byte[] done = new byte[8];
        putTag(done, 0, "DONE");
        Wire.writeLe32(done, 4, mtimeSec);
        out.write(done);
        out.flush();

        byte[] resp = new byte[8];
        Wire.readFully(in, resp);
        String code = new String(resp, 0, 4, StandardCharsets.US_ASCII);
        int len = Wire.readLe32(resp, 4);
        if ("OKAY".equals(code)) return total;

        byte[] msg = new byte[Math.min(Math.max(0, len), MAX_FAIL_MSG)];
        if (msg.length > 0) Wire.readFully(in, msg);
        throw new IOException("sync " + code + ": "
                + new String(msg, StandardCharsets.UTF_8));
    }

    private static void putTag(byte[] dst, int off, String tag) {
        byte[] b = tag.getBytes(StandardCharsets.US_ASCII);
        System.arraycopy(b, 0, dst, off, 4);
    }
}
