package me.proxylink.common;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public final class FrameCodec {
    private final DataInputStream input;
    private final DataOutputStream output;

    public FrameCodec(InputStream input, OutputStream output) {
        this.input = new DataInputStream(input);
        this.output = new DataOutputStream(output);
    }

    public Frame readFrame() throws IOException {
        int magic;
        try {
            magic = input.readInt();
        } catch (EOFException eof) {
            return null;
        }

        if (magic != ProtocolConstants.MAGIC) {
            throw new ProtocolException("Invalid frame magic");
        }

        int version = Short.toUnsignedInt(input.readShort());
        if (version != ProtocolConstants.PROTOCOL_VERSION) {
            throw new ProtocolException("Unsupported protocol version: " + version);
        }

        int typeCode = Byte.toUnsignedInt(input.readByte());
        input.readByte();
        long streamId = input.readLong();
        int payloadLength = input.readInt();

        if (payloadLength < 0 || payloadLength > ProtocolConstants.MAX_FRAME_PAYLOAD_BYTES) {
            throw new ProtocolException("Invalid frame payload length: " + payloadLength);
        }

        byte[] payload = input.readNBytes(payloadLength);
        if (payload.length != payloadLength) {
            throw new EOFException("Unexpected end of stream while reading frame payload");
        }

        return new Frame(FrameType.fromCode(typeCode), streamId, payload);
    }

    public void writeFrame(Frame frame) throws IOException {
        byte[] payload = frame.payload();
        if (payload.length > ProtocolConstants.MAX_FRAME_PAYLOAD_BYTES) {
            throw new ProtocolException("Frame payload is too large: " + payload.length);
        }

        synchronized (output) {
            output.writeInt(ProtocolConstants.MAGIC);
            output.writeShort(ProtocolConstants.PROTOCOL_VERSION);
            output.writeByte(frame.type().code());
            output.writeByte(0);
            output.writeLong(frame.streamId());
            output.writeInt(payload.length);
            output.write(payload);
            output.flush();
        }
    }
}
