/*
 * Copyright 2010 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package java.io;

import java.nio.channels.FileChannel;

/** Saves binary data to the local storage; currently using hex encoding. The string is prefixed with "hex:"
 * @author haustein */
public class RandomAccessFile /* implements DataOutput, DataInput, Closeable */ {

/*
 * public final FileDescriptor getFD() throws IOException { }
 */
	final String name;
	final boolean writeable;
	boolean dirty;
	String data;
	int newDataPos;
	StringBuilder newData;
	int pos;
	int len;

	DataInputStream dis = new DataInputStream(new RafInputStream());
	DataOutputStream dos = new DataOutputStream(new RafOutputStream());

	public RandomAccessFile (String name, String mode) throws FileNotFoundException {
		this(new File(name), mode);
	}

	public RandomAccessFile (File file, String mode) throws FileNotFoundException {
		name = file.getCanonicalPath();

		mode = mode.toLowerCase();
		if (!mode.equals("r") && !mode.equals("rw")) {
			throw new IllegalArgumentException("mode: '" + mode + "'");
		}
		writeable = mode.equals("rw");
		if (file.exists()) {
			data = atob(File.LocalStorage.getItem(name));
			len = data.length();
		} else if (writeable) {
			data = "";
			dirty = true;
			try {
				flush();
			} catch (IOException e) {
				throw new FileNotFoundException("" + e);
			}
		} else {
			throw new FileNotFoundException(name);
		}
	}

	static native String btoa (String s) /*-{
														return $wnd.btoa(s);
														}-*/;

	static native String atob (String s) /*-{
														return $wnd.atob(s);
														}-*/;

	public int read () throws IOException {
		return dis.read();
	}

	public int read (byte b[], int off, int len) throws IOException {
		return dis.read(b, off, len);
	}

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

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

	public final void readFully (byte b[], int off, int len) throws IOException {
		dis.readFully(b, off, len);
	}

	public int skipBytes (int n) throws IOException {
		return dis.skipBytes(n);
	}

	public void write (int b) throws IOException {
		dos.write(b);
	};

	public void write (byte b[]) throws IOException {
		dos.write(b);
	}

	public void write (byte b[], int off, int len) throws IOException {
		dos.write(b, off, len);
	}

	public long getFilePointer () throws IOException {
		return pos;
	}

	public void seek (long pos) throws IOException {
		if (pos < 0) {
			throw new IllegalArgumentException();
		}
		this.pos = (int)pos;
	}

	public long length () throws IOException {
		return len;
	}

	public void setLength (long newLength) throws IOException {
		if (len != newLength) {
			consolidate();
			if (data.length() > newLength) {
				data = data.substring(0, (int)newLength);
				len = (int)newLength;
			} else {
// System.out.println("padding " + (newLength - len) + " zeros in setLength to " + newLength);
				while (len < newLength) {
					write(0);
				}
			}
		}
	}

	public void close () throws IOException {
		if (data != null) {
			flush();
			data = null;
		}
	}

	void consolidate () {
		if (newData == null) {
			return;
		}

// System.out.println("consolidate(); ndp: " + newDataPos + " nd-len: " + newData.length());

		if (data.length() < newDataPos) {
			StringBuilder filler = new StringBuilder();
			while (data.length() + filler.length() < newDataPos) {
				filler.append('\0');
			}
// System.out.println("padding " + (filler.length()) + " zeros in consolidate ");

			data += filler.toString();
		}

		int p2 = newDataPos + newData.length();
		data = data.substring(0, newDataPos) + newData.toString() + (p2 < data.length() ? data.substring(p2) : "");
		newData = null;
	}

	void flush () throws IOException {
		if (!dirty) {
			return;
		}
		consolidate();
		File.LocalStorage.setItem(name, btoa(data));
		dirty = false;
	}

	public final boolean readBoolean () throws IOException {
		return dis.readBoolean();
	}

	public final byte readByte () throws IOException {
		return dis.readByte();
	}

	public final int readUnsignedByte () throws IOException {
		return dis.readUnsignedByte();
	}

	public final short readShort () throws IOException {
		return dis.readShort();
	}

	public final int readUnsignedShort () throws IOException {
		return dis.readUnsignedShort();
	}

	public final char readChar () throws IOException {
		return dis.readChar();
	}

	public final int readInt () throws IOException {
		return dis.readInt();
	}

	public final long readLong () throws IOException {
		return dis.readLong();
	}

	public final float readFloat () throws IOException {
		return dis.readFloat();
	}

	public final double readDouble () throws IOException {
		return dis.readDouble();
	}

	public final String readLine () throws IOException {
		return dis.readLine();
	}

	public final String readUTF () throws IOException {
		return dis.readUTF();
	}

	public final void writeBoolean (boolean v) throws IOException {
		dos.writeBoolean(v);
	}

	public final void writeByte (int v) throws IOException {
		dos.writeByte(v);
	}

	public final void writeShort (int v) throws IOException {
		dos.writeShort(v);
	}

	public final void writeChar (int v) throws IOException {
		dos.writeChar(v);
	}

	public final void writeInt (int v) throws IOException {
		dos.writeInt(v);
	}

	public final void writeLong (long v) throws IOException {
		dos.writeLong(v);
	}

	public final void writeFloat (float v) throws IOException {
		dos.writeFloat(v);
	}

	public final void writeDouble (double v) throws IOException {
		dos.writeDouble(v);
	}

	public final void writeBytes (String s) throws IOException {
		dos.writeBytes(s);
	}

	public final void writeChars (String s) throws IOException {
		dos.writeChars(s);
	}

	public final void writeUTF (String str) throws IOException {
		dos.writeUTF(str);
	}

	public final FileChannel getChannel () throws IOException {
		return null;
	}

	class RafInputStream extends InputStream {
		@Override
		public int read () throws IOException {
			if (pos >= len) {
				return -1;
			} else {
				consolidate();
				return data.charAt(pos++);
// int p2 = (pos << 1);
// int result = Character.digit(data.charAt(p2), 16) * 16 +
// Character.digit(data.charAt(p2 + 1), 16);
// pos++;
// return result;
			}
		}
	}

	class RafOutputStream extends OutputStream {

		public void write (int b) throws IOException {
			if (!writeable) {
				throw new IOException("not writeable");
			}

			if (newData == null) {
				newDataPos = pos;
				newData = new StringBuilder();
// System.out.println("no buf; newDataPos: " + pos);
			} else if (newDataPos + newData.length() != pos) {
				consolidate();
				newDataPos = pos;
				newData = new StringBuilder();
// System.out.println("pos mismatch; newDataPos: " + pos);
			}

			newData.append((char)(b & 255));
// newData.append("" + Character.forDigit((b >> 4) & 15, 16) +
// Character.forDigit(b & 15, 16));
			pos++;
			len = Math.max(pos, len);
			dirty = true;
		}
	}
}
