namespace se.lth.control.labcomm2014 {

  using System;
  using System.IO;
  using System.Text;
  using System.Runtime.InteropServices;

  public class EncoderChannel : Encoder {

    private Stream writer;
    private MemoryStream bytes = new MemoryStream();
    private EncoderRegistry def_registry = new EncoderRegistry();
    private EncoderRegistry ref_registry = new EncoderRegistry();
    byte[] buf = new byte[8];
    private int current_tag; 

    public EncoderChannel(Stream writer) {
      this.writer = writer;

      begin(Constant.VERSION);
      encodeString(Constant.CURRENT_VERSION);
      end(null);
    }

    public void register(SampleDispatcher dispatcher) {
      int index = def_registry.add(dispatcher);
      begin(Constant.SAMPLE_DEF);
      encodePacked32(index);
      encodeString(dispatcher.getName());
      byte[] signature = dispatcher.getSignature();
      encodePacked32(signature.Length);
      for (int i = 0 ; i < signature.Length ; i++) {
	encodeByte(signature[i]);
      }
      end(null);
    }

    public void registerSampleRef(SampleDispatcher dispatcher) {
      int index = ref_registry.add(dispatcher);
      begin(Constant.SAMPLE_REF);
      encodePacked32(index);
      encodeString(dispatcher.getName());
      byte[] signature = dispatcher.getSignature();
      encodePacked32(signature.Length);
      for (int i = 0 ; i < signature.Length ; i++) {
	encodeByte(signature[i]);
      }
      end(null);
    }

    private void begin(int tag) {
      current_tag = tag;
      bytes.SetLength(0);
    }

    public void begin(SampleDispatcher identity) {
      begin(def_registry.getTag(identity));
    }

    public void end(SampleDispatcher identity) {
      WritePacked32(writer, current_tag);
      WritePacked32(writer, bytes.Length);
      bytes.WriteTo(writer);
      bytes.SetLength(0);
      writer.Flush();
    }

    private void WritePacked32(Stream s, Int64 value) {
      Int64 v = value & 0xffffffff;
      int i;
  
      for (i = 0 ; i == 0 || v != 0 ; i++, v = (v >> 7)) {
        buf[i] = (byte)(v & 0x7f | (i!=0?0x80:0x00));
      }
      for (i = i - 1 ; i >= 0 ; i--) {
        s.WriteByte(buf[i]);
      }      
    }

    private void WriteInt(Int64 value, int length) {
      for (int i = length - 1 ; i >= 0 ; i--) {
	buf[i] = (byte)(value & 0xff);
	value = value >> 8;
      }
      bytes.Write(buf, 0, length);
    }

    public void encodeBoolean(bool value) {
      WriteInt(value ? 1 : 0, 1);
    }

    public void encodeByte(byte value) {
      WriteInt(value, 1);
    }

    public void encodeShort(short value) {
      WriteInt(value, 2);
    }

    public void encodeInt(int value) {
      WriteInt(value, 4);
    }

    public void encodeLong(long value) {
      WriteInt(value, 8);
    }

    [StructLayout(LayoutKind.Explicit)]
    private struct Int32SingleUnion {
      [FieldOffset(0)] public int AsInt;
      [FieldOffset(0)] public float AsFloat;
    };

    public void encodeFloat(float value) {
      Int32SingleUnion u;
      u.AsInt = 0; // Avoid error messge
      u.AsFloat = value;
      WriteInt(u.AsInt, 4);
    }

    public void encodeDouble(double value) {
      WriteInt(BitConverter.DoubleToInt64Bits(value), 8);
    }

    public void encodeString(String value) {
      byte[] buf = Encoding.UTF8.GetBytes(value);
      encodePacked32(buf.Length);
      bytes.Write(buf, 0, buf.Length);
    }

    public void encodePacked32(Int64 value) {
      WritePacked32(bytes, value);
    }

    public void encodeSampleRef(SampleDispatcher value) {
      int index = 0;
      try {
        index = ref_registry.getTag(value);
      } catch (NullReferenceException) {
        // encode 0 for unregistered ref types
      }
      WriteInt(index, 4);
    }

  }
}