package com.jscape.inet.sftp.protocol.v4.marshaling;

import com.jscape.inet.sftp.protocol.messages.Message;
import com.jscape.inet.sftp.protocol.v4.messages.SshFxpExtendedReply;
import com.jscape.inet.sftp.protocol.v4.messages.SshFxpExtendedReplyRaw;
import com.jscape.inet.ssh.protocol.v2.marshaling.primitive.Uint32Codec;
import com.jscape.util.IOTools;
import com.jscape.util.io.ByteArrayInputStream;
import com.jscape.util.io.DataCodec;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import static com.jscape.util.Parameters.assertNotNull;

/**
 * @author Alex Usun
 */
public class SshFxpExtendedReplyCodec
        implements DataCodec<Message> {

    private final Map<Class, Entry> entries;

    public SshFxpExtendedReplyCodec(Entry... entries) {
        this.entries = new HashMap<>();
        set(entries);
    }

    public SshFxpExtendedReplyCodec set(Entry... entries) {
        for (Entry entry : entries) {
            this.entries.put(entry.messageClass, entry);
        }

        return this;
    }

    @Override
    public Message read(InputStream in)
            throws IOException {
        int id = Uint32Codec.readValue(in);
        byte[] data = IOTools.readFully(in);

        return new SshFxpExtendedReplyRaw(id, data);
    }

    @SuppressWarnings("unchecked")
    public <M extends SshFxpExtendedReply> M convert(SshFxpExtendedReplyRaw message, Class<M> messageClass)
            throws IOException {
        ByteArrayInputStream in = new ByteArrayInputStream(message.data);
        Entry entry = entryFor(messageClass);

        return (M) entry.codec.read(in, message.id);
    }

    @Override
    public void write(Message value, OutputStream out)
            throws IOException {
        SshFxpExtendedReply message = (SshFxpExtendedReply) value;

        Entry entry = entryFor(message);

        Uint32Codec.writeValue(message.id, out);
        entry.codec.write(message, out);
    }

    private Entry entryFor(SshFxpExtendedReply message) {
        return entryFor(message.getClass());
    }

    private Entry entryFor(Class messageClass) {
        Entry entry = this.entries.get(messageClass);

        if (entry == null) {
            throw new IllegalArgumentException("Unsupported message: " + messageClass);
        }

        return entry;
    }

    public interface TypeCodec {

        SshFxpExtendedReply read(InputStream in, int id)
                throws IOException;

        void write(SshFxpExtendedReply value, OutputStream out)
                throws IOException;

    }

    public static class Entry {

        public final TypeCodec codec;
        public final Class messageClass;

        public Entry(TypeCodec codec, Class messageClass) {
            assertNotNull(codec);
            this.codec = codec;

            assertNotNull(messageClass);
            this.messageClass = messageClass;
        }

        @Override
        public String toString() {
            return "Entry {"
                   + "codec=" + this.codec
                   + ", messageClasses=" + this.messageClass
                   + '}';
        }
    }
}
