/*
 * Copyright (c) 2002-2011 JSCAPE
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 *  the terms of the license agreement you entered into with JSCAPE.
 */

package com.jscape.inet.scp.protocol.marshaling;

import com.jscape.inet.scp.protocol.messages.Message;
import com.jscape.util.io.DataCodec;
import com.jscape.util.io.primitive.ByteCodec;

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

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

    private final Map<Integer, Entry> readingEntries;
    private final Map<Class, Entry> writingEntries;

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

    public MessageCodec set(Entry... entries) {
        for (Entry entry : entries) {
            this.readingEntries.put(entry.messageId, entry);
            this.writingEntries.put(entry.messageClass, entry);
        }

        return this;
    }

    public Message read(InputStream in)
            throws IOException {
        int id = ByteCodec.readValue(in) & 0xFF;
        Entry entry = entryFor(id);
    
        return entry.codec.read(in);
    }
    
    public void write(Message value, OutputStream out)
            throws IOException {
        Entry entry = entryFor(value);
    
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        ByteCodec.writeValue((byte) entry.messageId, buffer);
        entry.codec.write(value, buffer);

        buffer.writeTo(out);
        out.flush();
    }
    
    @Override
    public String toString() {
        return "MessageCodec {"
                + "readingEntries=" + this.readingEntries
                + ", writingEntries=" + this.writingEntries
                + '}';
    }
    
    public Entry entryFor(int id) {
        return this.readingEntries.get(id);
    }
    
    public Entry entryFor(Message message) {
        Entry entry = this.writingEntries.get(message.getClass());
    
        if (entry == null) {
            throw new IllegalArgumentException(String.format("Unsupported message: %s.", message.getClass()));
        }
    
        return entry;
    }

    public static class Entry {

        public final int messageId;
        public final Class messageClass;
        public final DataCodec<Message> codec;

        public static Entry entry(int messageId, Class messageClass, DataCodec<Message> codec) {
            return new Entry(messageId, messageClass, codec);
        }

        public Entry(int messageId, Class messageClass, DataCodec<Message> codec) {
            this.messageId = messageId;
            this.messageClass = messageClass;
            this.codec = codec;
        }

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