/*
 * This file is part of Nuts Framework.
 * Copyright(C) 2009-2012 Nuts Develop Team.
 *
 * Nuts Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License any later version.
 * 
 * Nuts Framework is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Nuts Framework. If not, see <http://www.gnu.org/licenses/>.
 */
package nuts.exts.vfs.ndfs;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.vfs2.RandomAccessContent;
import org.apache.commons.vfs2.util.RandomAccessMode;

/**
 * RAM File Random Access Content
 */
public class NdfsFileRandomAccessContent implements RandomAccessContent {
	private byte buffer8[] = new byte[8];
	private byte buffer4[] = new byte[4];
	private byte buffer2[] = new byte[2];
	private byte buffer1[] = new byte[1];


	private byte[] fileData;
	private int filePointer;
	private boolean dirty;

	private NdfsFileObject file;
	private InputStream rafis;

	/**
	 * @param file file
	 * @param mode mode
	 * @throws Exception if an error occurs 
	 */
	public NdfsFileRandomAccessContent(NdfsFileObject file, RandomAccessMode mode) throws Exception {
		super();

		this.file = file;
		this.fileData = file.getFile().getData();
		this.filePointer = 0;
		this.dirty = false;

		rafis = new InputStream() {
			public int read() throws IOException {
				try {
					return readByte();
				}
				catch (EOFException e) {
					return -1;
				}
			}

			public long skip(long n) throws IOException {
				seek(getFilePointer() + n);
				return n;
			}

			public void close() throws IOException {
			}

			public int read(byte b[]) throws IOException {
				return read(b);
			}

			public int read(byte b[], int off, int len) throws IOException {
				int retLen = Math.min(len, getLeftBytes());
				NdfsFileRandomAccessContent.this.readFully(b, off, retLen);
				return retLen;
			}

			public int available() throws IOException {
				return getLeftBytes();
			}
		};
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.apache.commons.vfs.RandomAccessContent#getFilePointer()
	 */
	public long getFilePointer() throws IOException {
		return filePointer;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.apache.commons.vfs.RandomAccessContent#seek(long)
	 */
	public void seek(long pos) throws IOException {
		filePointer = (int)pos;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.apache.commons.vfs.RandomAccessContent#length()
	 */
	public long length() throws IOException {
		return fileData.length;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.apache.commons.vfs.RandomAccessContent#close()
	 */
	public void close() throws IOException {
		file.saveData(dirty);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataInput#readByte()
	 */
	public byte readByte() throws IOException {
		return (byte)readUnsignedByte();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataInput#readChar()
	 */
	public char readChar() throws IOException {
		int ch1 = readUnsignedByte();
		int ch2 = readUnsignedByte();
		return (char)((ch1 << 8) + (ch2 << 0));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataInput#readDouble()
	 */
	public double readDouble() throws IOException {
		return Double.longBitsToDouble(readLong());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataInput#readFloat()
	 */
	public float readFloat() throws IOException {
		return Float.intBitsToFloat(readInt());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataInput#readInt()
	 */
	public int readInt() throws IOException {
		return (readUnsignedByte() << 24) | (readUnsignedByte() << 16)
				| (readUnsignedByte() << 8) | readUnsignedByte();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataInput#readUnsignedByte()
	 */
	public int readUnsignedByte() throws IOException {
		if (filePointer < fileData.length) {
			return fileData[filePointer++] & 0xFF;
		}
		else {
			throw new EOFException();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataInput#readUnsignedShort()
	 */
	public int readUnsignedShort() throws IOException {
		readFully(buffer2);
		return toUnsignedShort(buffer2);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataInput#readLong()
	 */
	public long readLong() throws IOException {
		readFully(buffer8);
		return toLong(buffer8);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataInput#readShort()
	 */
	public short readShort() throws IOException {
		readFully(buffer2);
		return toShort(buffer2);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataInput#readBoolean()
	 */
	public boolean readBoolean() throws IOException {
		return (readUnsignedByte() != 0);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataInput#skipBytes(int)
	 */
	public int skipBytes(int n) throws IOException {
		if (n < 0) {
			throw new IndexOutOfBoundsException(
					"The skip number can't be negative");
		}

		long newPos = filePointer + n;

		if (newPos > fileData.length) {
			throw new IndexOutOfBoundsException("Tyring to skip too much bytes");
		}

		seek(newPos);

		return n;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataInput#readFully(byte[])
	 */
	public void readFully(byte[] b) throws IOException {
		this.readFully(b, 0, b.length);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataInput#readFully(byte[], int, int)
	 */
	public void readFully(byte[] b, int off, int len) throws IOException {
		if (len < 0) {
			throw new IndexOutOfBoundsException("Length is lower than 0");
		}

		if (len > this.getLeftBytes()) {
			throw new IndexOutOfBoundsException("Read length (" + len
					+ ") is higher than buffer left bytes ("
					+ this.getLeftBytes() + ") ");
		}

		System.arraycopy(fileData, filePointer, b, off, len);

		filePointer += len;
	}

	private int getLeftBytes() {
		return fileData.length - filePointer;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataInput#readUTF()
	 */
	public String readUTF() throws IOException {
		return DataInputStream.readUTF(this);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataOutput#write(byte[], int, int)
	 */
	public void write(byte[] b, int off, int len) throws IOException {
		if (getLeftBytes() < len) {
			int newSize = fileData.length + len - getLeftBytes();
			file.resize(newSize);
			fileData = file.getFile().getData();
		}
		System.arraycopy(b, off, fileData, filePointer, len);
		filePointer += len;
		dirty = true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataOutput#write(byte[])
	 */
	public void write(byte[] b) throws IOException {
		write(b, 0, b.length);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataOutput#writeByte(int)
	 */
	public void writeByte(int i) throws IOException {
		write(i);
	}

	/**
	 * Build a long from first 8 bytes of the array.
	 * 
	 * @author Apache-Commons-Id Team
	 * @param b The byte[] to convert.
	 * @return A long.
	 */
	private static long toLong(byte[] b) {
		return ((((long)b[7]) & 0xFF) + ((((long)b[6]) & 0xFF) << 8)
				+ ((((long)b[5]) & 0xFF) << 16) + ((((long)b[4]) & 0xFF) << 24)
				+ ((((long)b[3]) & 0xFF) << 32) + ((((long)b[2]) & 0xFF) << 40)
				+ ((((long)b[1]) & 0xFF) << 48) + ((((long)b[0]) & 0xFF) << 56));
	}

	/**
	 * Build a 8-byte array from a long. No check is performed on the array
	 * length.
	 * 
	 * @author Commons-Id Team
	 * 
	 * @param n The number to convert.
	 * @param b The array to fill.
	 * @return A byte[].
	 */
	private static byte[] toBytes(long n, byte[] b) {
		b[7] = (byte)(n);
		n >>>= 8;
		b[6] = (byte)(n);
		n >>>= 8;
		b[5] = (byte)(n);
		n >>>= 8;
		b[4] = (byte)(n);
		n >>>= 8;
		b[3] = (byte)(n);
		n >>>= 8;
		b[2] = (byte)(n);
		n >>>= 8;
		b[1] = (byte)(n);
		n >>>= 8;
		b[0] = (byte)(n);
		return b;
	}

	/**
	 * Build a short from first 2 bytes of the array.
	 * 
	 * @author Apache-Commons-Id Team
	 * @param b The byte[] to convert.
	 * @return A short.
	 */
	private static short toShort(byte[] b) {
		return (short)toUnsignedShort(b);
	}

	/**
	 * Build a short from first 2 bytes of the array.
	 * 
	 * @author Apache-Commons-Id Team
	 * @param b The byte[] to convert.
	 * @return A short.
	 */
	private static int toUnsignedShort(byte[] b) {
		return ((b[1] & 0xFF) + ((b[0] & 0xFF) << 8));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataOutput#write(int)
	 */
	public void write(int b) throws IOException {
		buffer1[0] = (byte)b;
		write(buffer1);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataOutput#writeBoolean(boolean)
	 */
	public void writeBoolean(boolean v) throws IOException {
		write(v ? 1 : 0);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataOutput#writeBytes(java.lang.String)
	 */
	public void writeBytes(String s) throws IOException {
		write(s.getBytes());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataOutput#writeChar(int)
	 */
	public void writeChar(int v) throws IOException {
		buffer2[0] = (byte)((v >>> 8) & 0xFF);
		buffer2[1] = (byte)((v >>> 0) & 0xFF);
		write(buffer2);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataOutput#writeChars(java.lang.String)
	 */
	public void writeChars(String s) throws IOException {
		int len = s.length();
		for (int i = 0; i < len; i++) {
			writeChar(s.charAt(i));
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataOutput#writeDouble(double)
	 */
	public void writeDouble(double v) throws IOException {
		writeLong(Double.doubleToLongBits(v));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataOutput#writeFloat(float)
	 */
	public void writeFloat(float v) throws IOException {
		writeInt(Float.floatToIntBits(v));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataOutput#writeInt(int)
	 */
	public void writeInt(int v) throws IOException {
		buffer4[0] = (byte)((v >>> 24) & 0xFF);
		buffer4[1] = (byte)((v >>> 16) & 0xFF);
		buffer4[2] = (byte)((v >>> 8) & 0xFF);
		buffer4[3] = (byte)(v & 0xFF);
		write(buffer4);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataOutput#writeLong(long)
	 */
	public void writeLong(long v) throws IOException {
		write(toBytes(v, buffer8));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataOutput#writeShort(int)
	 */
	public void writeShort(int v) throws IOException {
		buffer2[0] = (byte)((v >>> 8) & 0xFF);
		buffer2[1] = (byte)(v & 0xFF);
		write(buffer2);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataOutput#writeUTF(java.lang.String)
	 */
	public void writeUTF(String str) throws IOException {
		ByteArrayOutputStream out = new ByteArrayOutputStream(str.length());
		DataOutputStream dataOut = new DataOutputStream(out);
		dataOut.writeUTF(str);
		dataOut.flush();
		dataOut.close();
		byte[] b = out.toByteArray();
		write(b);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.io.DataInput#readLine()
	 */
	public String readLine() throws IOException {
		throw new UnsupportedOperationException("deprecated");
	}

	public InputStream getInputStream() throws IOException {
		return rafis;
	}
}
