/*
 * @(#)DataAtomInputStream.java  
 * 
 * Copyright (c) 2012 Werner Randelshofer, Immensee, Switzerland.
 * All rights reserved.
 * 
 * You may not use, copy or modify this file, except in compliance with the
 * license agreement you entered into with Werner Randelshofer.
 * For details see accompanying license terms.
 */
package org.monte.media.quicktime;

import java.io.DataInputStream;
import java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.GregorianCalendar;

/**
 * {@code DataAtomInputStream}.
 *
 * @author Werner Randelshofer
 * @version $Id
 */
public class DataAtomInputStream extends FilterInputStream {

    protected static final long MAC_TIMESTAMP_EPOCH = new GregorianCalendar(1904, GregorianCalendar.JANUARY, 1).getTimeInMillis();
    private byte readBuffer[] = new byte[8];

    public DataAtomInputStream(InputStream in) {
        super(in);
    }

    public final byte readByte() throws IOException {
        int ch = in.read();
        if (ch < 0) {
            throw new EOFException();
        }
        return (byte) (ch);
    }

    public final short readShort() throws IOException {
        int ch1 = in.read();
        int ch2 = in.read();
        if ((ch1 | ch2) < 0) {
            throw new EOFException();
        }
        return (short) ((ch1 << 8) + (ch2 << 0));
    }

    public final int readInt() throws IOException {
        int ch1 = in.read();
        int ch2 = in.read();
        int ch3 = in.read();
        int ch4 = in.read();
        if ((ch1 | ch2 | ch3 | ch4) < 0) {
            throw new EOFException();
        }
        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
    }

    public final long readLong() throws IOException {
        readFully(readBuffer, 0, 8);
        return (((long) readBuffer[0] << 56)
                + ((long) (readBuffer[1] & 255) << 48)
                + ((long) (readBuffer[2] & 255) << 40)
                + ((long) (readBuffer[3] & 255) << 32)
                + ((long) (readBuffer[4] & 255) << 24)
                + ((readBuffer[5] & 255) << 16)
                + ((readBuffer[6] & 255) << 8)
                + ((readBuffer[7] & 255) << 0));
    }

    public final int readUShort() throws IOException {
        return readShort() & 0xFFFF;
    }

    public final long readUInt() throws IOException {
        return readInt() & 0xFFFFFFFFL;
    }

    public final long skipBytes(long n) throws IOException {
        long total = 0;
        long cur = 0;

        while ((total < n) && ((cur = (int) in.skip(n - total)) > 0)) {
            total += cur;
        }

        return total;
    }

    public final void readFully(byte b[]) throws IOException {
        readFully(b, 0, b.length);
    }

    public final void readFully(byte b[], int off, int len) throws IOException {
        if (len < 0) {
            throw new IndexOutOfBoundsException();
        }
        int n = 0;
        while (n < len) {
            int count = in.read(b, off + n, len - n);
            if (count < 0) {
                throw new EOFException();
            }
            n += count;
        }
    }

    /**
     * Reads a 32-bit Mac timestamp (seconds since 1902).
     * @return date
     * @throws java.io.IOException
     */
    public Date readMacTimestamp() throws IOException {
        long timestamp = ((long) readInt()) & 0xffffffffL;
        return new Date(MAC_TIMESTAMP_EPOCH + timestamp * 1000);
    }

    /**
     * Reads 32-bit fixed-point number divided as 16.16.
     */
    public double readFixed16D16() throws IOException {
        int wholePart = readUShort();
        int fractionPart = readUShort();

        return new Double(wholePart + fractionPart / 65536.0);
    }

    /**
     * Reads 32-bit fixed-point number divided as 2.30.
     */
    public double readFixed2D30() throws IOException {
        int fixed = readInt();
        int wholePart = fixed >>> 30;
        int fractionPart = fixed & 0x3fffffff;

        return new Double(wholePart + fractionPart / (double) 0x3fffffff);
    }

    /**
     * Reads 16-bit fixed-point number divided as 8.8.
     */
    public double readFixed8D8() throws IOException {
        int fixed = readUShort();
        int wholePart = fixed >>> 8;
        int fractionPart = fixed & 0xff;

        return new Double(wholePart + fractionPart / 256f);
    }

    public String readType() throws IOException {
        int id = readInt();
        byte[] b = new byte[4];
        b[0] = (byte) ((id >>> 24) & 0xff);
        b[1] = (byte) ((id >>> 16) & 0xff);
        b[2] = (byte) ((id >>> 8) & 0xff);
        b[3] = (byte) (id & 0xff);
        try {
            return new String(b, "ASCII");
        } catch (UnsupportedEncodingException ex) {
            InternalError ie = new InternalError("ASCII not supported");
            ie.initCause(ex);
            throw ie;
        }
    }

    public String readPString() throws IOException {
        int size = in.read();
        if (size == 0) {
            size = in.read();
            in.read(); // why do we skip two bytes here?
            in.read();
        }
        if (size < 0) {
            return "";
        }
        byte[] b = new byte[size];
        //in.readFully(bytes);
        int n = 0;
        while (n < size) {
            int count = in.read(b, n, size - n);
            if (count < 0) {
                System.out.println("StructParser.PrimitiveSpecifier.read not enough bytes for pstring. Expected size:" + size + " actual size:" + n);
                break;
                //throw new EOFException();
            }
            n += count;
        }

        try {
            return new String(b, "ASCII");
        } catch (UnsupportedEncodingException ex) {
            InternalError ie = new InternalError("ASCII not supported");
            ie.initCause(ex);
            throw ie;
        }
    }
}
