package com.ljyloo.io;

import java.io.DataInput;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UTFDataFormatException;

import com.google.common.base.Preconditions;
import com.google.common.io.ByteStreams;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;

public final class LittleEndianDataInputStream extends FilterInputStream implements DataInput {

  public LittleEndianDataInputStream(InputStream in) {
    super(Preconditions.checkNotNull(in));
  }

  @Override
  public String readLine() {
    throw new UnsupportedOperationException("readLine is not supported");
  }

  @Override
  public void readFully(byte[] b) throws IOException {
    ByteStreams.readFully(this, b);
  }

  @Override
  public void readFully(byte[] b, int off, int len) throws IOException {
    ByteStreams.readFully(this, b, off, len);
  }

  @Override
  public int skipBytes(int n) throws IOException {
    return (int) in.skip(n);
  }

  @Override
  public int readUnsignedByte() throws IOException {
    int b1 = in.read();
    if (0 > b1) {
      throw new EOFException();
    }

    return b1;
  }

  @Override
  public int readUnsignedShort() throws IOException {
    byte b1 = readAndCheckByte();
    byte b2 = readAndCheckByte();

    return Ints.fromBytes((byte) 0, (byte) 0, b2, b1);
  }

  @Override
  public int readInt() throws IOException {
    byte b1 = readAndCheckByte();
    byte b2 = readAndCheckByte();
    byte b3 = readAndCheckByte();
    byte b4 = readAndCheckByte();

    return Ints.fromBytes(b4, b3, b2, b1);
  }

  @Override
  public long readLong() throws IOException {
    byte b1 = readAndCheckByte();
    byte b2 = readAndCheckByte();
    byte b3 = readAndCheckByte();
    byte b4 = readAndCheckByte();
    byte b5 = readAndCheckByte();
    byte b6 = readAndCheckByte();
    byte b7 = readAndCheckByte();
    byte b8 = readAndCheckByte();

    return Longs.fromBytes(b8, b7, b6, b5, b4, b3, b2, b1);
  }

  @Override
  public float readFloat() throws IOException {
    return Float.intBitsToFloat(readInt());
  }

  @Override
  public double readDouble() throws IOException {
    return Double.longBitsToDouble(readLong());
  }

  public String readUTF() throws IOException {
      int utflen = this.readUnsignedShort();
      byte[] bytearr = new byte[utflen];
      char[] chararr = new char[utflen];

      int c, char2, char3;
      int count = 0;
      int chararr_count=0;

      this.readFully(bytearr, 0, utflen);

      while (count < utflen) {
          c = (int) bytearr[count] & 0xff;
          if (c > 127) break;
          count++;
          chararr[chararr_count++]=(char)c;
      }

      while (count < utflen) {
          c = (int) bytearr[count] & 0xff;
          switch (c >> 4) {
              case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
                  /* 0xxxxxxx*/
                  count++;
                  chararr[chararr_count++]=(char)c;
                  break;
              case 12: case 13:
                  /* 110x xxxx   10xx xxxx*/
                  count += 2;
                  if (count > utflen)
                      throw new UTFDataFormatException(
                          "malformed input: partial character at end");
                  char2 = (int) bytearr[count-1];
                  if ((char2 & 0xC0) != 0x80)
                      throw new UTFDataFormatException(
                          "malformed input around byte " + count);
                  chararr[chararr_count++]=(char)(((c & 0x1F) << 6) |
                                                  (char2 & 0x3F));
                  break;
              case 14:
                  /* 1110 xxxx  10xx xxxx  10xx xxxx */
                  count += 3;
                  if (count > utflen)
                      throw new UTFDataFormatException(
                          "malformed input: partial character at end");
                  char2 = (int) bytearr[count-2];
                  char3 = (int) bytearr[count-1];
                  if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))
                      throw new UTFDataFormatException(
                          "malformed input around byte " + (count-1));
                  chararr[chararr_count++]=(char)(((c     & 0x0F) << 12) |
                                                  ((char2 & 0x3F) << 6)  |
                                                  ((char3 & 0x3F) << 0));
                  break;
              default:
                  /* 10xx xxxx,  1111 xxxx */
                  throw new UTFDataFormatException(
                      "malformed input around byte " + count);
          }
      }
      // The number of chars produced may be less than utflen
      return new String(chararr, 0, chararr_count);
  }

  @Override
  public short readShort() throws IOException {
    return (short) readUnsignedShort();
  }

  @Override
  public char readChar() throws IOException {
    return (char) readUnsignedShort();
  }

  @Override
  public byte readByte() throws IOException {
    return (byte) readUnsignedByte();
  }

  @Override
  public boolean readBoolean() throws IOException {
    return readUnsignedByte() != 0;
  }

  private byte readAndCheckByte() throws IOException, EOFException {
    int b1 = in.read();

    if (-1 == b1) {
      throw new EOFException();
    }

    return (byte) b1;
  }
}