package com.one;

import com.one.file.Connector;
import com.one.file.FileConnection;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;

public class ByteArrayFileConnection implements FileConnection
{
	public static final int DEFAULT_INITIAL_BUFFER_SIZE = 32;

	protected byte[] buf;
	protected int count, delta;

	protected InputStream iis;
	protected OutputStream ios;

	protected String basepath;
	protected FileConnection temp;

	protected InputStream ris;
	protected OutputStream ros;

	protected class InternalInputStream extends InputStream
	{
		protected int currpos = 0;
		protected int markedpos = 0;

		public InternalInputStream() throws IOException
		{
			init();
		}

		protected void init() throws IOException
		{
			if(buf == null && ris == null)
			{
				ris = temp.openInputStream();
				ris.skip(currpos);
			}
		}

		public int available() throws IOException
		{
			init();

			if(ris != null)
			{
				return ris.available();
			}
			else
			{
				return count - currpos;
			}
		}

		public void close() throws IOException
		{
			if(ris != null)
			{
				ris.close();
				ris = null;
			}

			iis = null;
		}

		public void mark(int readLimit)
		{
			try
			{
				init();

				if(ris != null)
				{
					ris.mark(readLimit);
				}
				else
				{
					markedpos = currpos;
				}
			}
			catch(IOException ioe)
			{
			}
		}

		public boolean markSupported()
		{
			try
			{
				init();

				if(ris != null)
				{
					return ris.markSupported();
				}
				else
				{
					return true;
				}
			}
			catch(IOException ioe)
			{
				return false;
			}
		}

		public int read() throws IOException
		{
			init();

			if(ris != null)
			{
				return ris.read();
			}
			else
			{
				if(currpos >= count)
				{
					return -1;
				}

				return (int)buf[currpos++] & 0xFF;
			}
		}

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

		public int read(byte[] b, int off, int len) throws IOException
		{
			init();

			if(ris != null)
			{
				return ris.read(b, off, len);
			}
			else
			{
				int available = available();

				if(available <= 0)
				{
					return -1;
				}

				if(len > available)
				{
					len = available;
				}

				System.arraycopy(buf, currpos, b, off, len);
				currpos += len;

				return len;
			}
		}

		public void reset() throws IOException
		{
			init();

			if(ris != null)
			{
				ris.reset();
			}
			else
			{
				currpos = markedpos;
			}
		}

		public long skip(long n) throws IOException
		{
			init();

			if(ris != null)
			{
				return ris.skip(n);
			}
			else
			{
				if(n > available())
				{
					n = available();
				}

				currpos += n;

				return n;
			}
		}
	}

	protected class InternalOutputStream extends OutputStream
	{
		protected int currpos;

		public InternalOutputStream() throws IOException
		{
			this(0);
		}

		public InternalOutputStream(int off) throws IOException
		{
			if(buf == null && ros == null)
			{
				ros = temp.openOutputStream(off);
			}

			currpos = off;
		}

		public void close() throws IOException
		{
			if(ros != null)
			{
				ros.close();
				ros = null;
			}

			ios = null;
		}

		public void flush() throws IOException
		{
			if(ros != null)
			{
				ros.flush();
			}
		}

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

		public void write(byte[] b, int off, int len) throws IOException
		{
			if(ros != null)
			{
				ros.write(b, off, len);
			}
			else
			{
				try
				{
					resize(len);
					System.arraycopy(b, off, buf, currpos, len);
					currpos += len;

					if(currpos > count)
					{
						count = currpos;
					}
				}
				catch(RuntimeException re)
				{
					createTempFile();
					write(b, off, len);
				}
			}
		}

		public void write(int b) throws IOException
		{
			if(ros != null)
			{
				ros.write(b);
			}
			else
			{
				try
				{
					resize(1);
					buf[currpos++] = (byte)b;

					if(currpos > count)
					{
						count = currpos;
					}
				}
				catch(RuntimeException re)
				{
					createTempFile();
					write(b);
				}
			}
		}

		protected void resize(int add) throws RuntimeException
		{
			if(currpos + add > buf.length)
			{
				int newlen = buf.length + (delta > 0 ? delta : buf.length);

				if(currpos + add > newlen)
				{
					newlen = currpos + add;
				}

				try
				{
					byte[] newbuf = new byte[newlen];
					System.arraycopy(buf, 0, newbuf, 0, currpos);
					buf = newbuf;
				}
				catch(OutOfMemoryError oome)
				{
					throw new RuntimeException("Failed to expand buffer from " + buf.length + " to " + newlen);
				}
			}
		}

		protected void createTempFile() throws IOException
		{
			if(temp == null)
			{
				temp = (FileConnection)Connector.open("file:///" + Connector.createTempFile(basepath, false));
				temp.create();

				OutputStream os = temp.openOutputStream();
				os.write(buf, 0, count);
				os.close();

				buf = null;
				System.gc();
			}

			ros = temp.openOutputStream(currpos);
		}
	}

	public ByteArrayFileConnection(String path, int size, int increment)
	{
		basepath = path;

		if(size < DEFAULT_INITIAL_BUFFER_SIZE)
		{
			size = DEFAULT_INITIAL_BUFFER_SIZE;
		}

		buf = new byte[size];
		delta = increment;
		count = 0;
	}
	
	public void close() throws IOException
	{
		if(iis != null)
		{
			iis.close();
		}

		if(ios != null)
		{
			ios.close();
		}

		if(temp != null)
		{
			Connector.releaseTempFile(temp.getPath() + temp.getName());

			temp.delete();
			temp.close();
			temp = null;
		}

		buf = null;
		System.gc();
	}
	
	protected void checkClosed() throws IOException
	{
		if(buf == null && temp == null)
		{
			throw new IOException("Connection is closed");
		}
	}

	public long availableSize()
	{
		if(temp != null)
		{
			return temp.availableSize();
		}
		else
		{
			return Runtime.getRuntime().freeMemory();
		}
	}

	public boolean canRead()
	{
		return true;
	}

	public boolean canWrite()
	{
		return true;
	}

	public void create()
	{
	}

	public void delete()
	{
	}

	public long directorySize(boolean includeSubDirs) throws IOException
	{
		throw new IOException();
	}

	public boolean exists()
	{
		return true;
	}

	public long fileSize() throws IOException
	{
		if(temp != null)
		{
			return temp.fileSize();
		}
		else
		{
			return count;
		}
	}

	public String getName()
	{
		return null;
	}

	public String getPath()
	{
		return null;
	}

	public String getURL()
	{
		return null;
	}

	public boolean isDirectory()
	{
		return false;
	}

	public boolean isHidden()
	{
		return false;
	}

	public boolean isOpen()
	{
		return buf != null || temp != null;
	}

	public long lastModified()
	{
		return System.currentTimeMillis();
	}

	public Enumeration list() throws IOException
	{
		throw new IOException();
	}

	public Enumeration list(String filter, boolean includeHidden) throws IOException
	{
		throw new IOException();
	}

	public void mkdir() throws IOException
	{
		throw new IOException();
	}

	public DataInputStream openDataInputStream() throws IOException
	{
		return new DataInputStream(openInputStream());
	}

	public DataOutputStream openDataOutputStream() throws IOException
	{
		return new DataOutputStream(openOutputStream());
	}

	public InputStream openInputStream() throws IOException
	{
		checkClosed();

		if(iis == null)
		{
			iis = new InternalInputStream();
		}

		return iis;
	}

	public OutputStream openOutputStream() throws IOException
	{
		checkClosed();

		if(ios == null)
		{
			ios = new InternalOutputStream();
		}

		return ios;
	}

	public OutputStream openOutputStream(long byteOffset) throws IOException
	{
		checkClosed();

		if(ios == null)
		{
			ios = new InternalOutputStream((int)byteOffset);
		}

		return ios;
	}

	public void rename(String newName) throws IOException
	{
		throw new IOException();
	}

	public void setFileConnection(String fileName) throws IOException
	{
		throw new IOException();
	}

	public void setHidden(boolean hidden)
	{
	}

	public void setReadable(boolean readable)
	{
	}

	public void setWritable(boolean writable)
	{
	}

	public long totalSize()
	{
		if(temp != null)
		{
			return temp.totalSize();
		}
		else
		{
			return Runtime.getRuntime().totalMemory();
		}
	}

	public void truncate(long byteOffset) throws IOException
	{
		if(temp != null)
		{
			temp.truncate(byteOffset);
		}
		else
		{
			count = (int)byteOffset;

			if(count < (delta > 0 ? buf.length - delta * 2 : buf.length / 4))
			{
				int newlen = count + (delta > 0 ? delta : count);

				if(newlen < DEFAULT_INITIAL_BUFFER_SIZE)
				{
					newlen = DEFAULT_INITIAL_BUFFER_SIZE;
				}

				try
				{
					byte[] newbuf = new byte[newlen];
					System.arraycopy(buf, 0, newbuf, 0, count);
					buf = newbuf;
				}
				catch(OutOfMemoryError oome)
				{
				}
			}
		}
	}
	
	public long usedSize()
	{
		if(temp != null)
		{
			return temp.usedSize();
		}
		else
		{
			return totalSize() - availableSize();
		}
	}

//	private void out(String text)
//	{
//		System.out.println("[" + Integer.toHexString(hashCode()).toUpperCase() + "] " + text);
//	}
}