package com.one;

import com.one.file.Connector;
import com.one.file.FileConnection;
import com.vmx.AuxClass;
import com.vmx.Locale;
import com.vmx.ProgressCallback;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Hashtable;

public abstract class ArchiveContainer extends Container
{
	protected FileConnection file;
	protected RandomAccessInputStream rais;
	protected boolean closed;
	protected boolean finished;

	protected Hashtable entries;
	protected Hashtable files;

	public static boolean virtualizeToRAM = true;

	public void init(String filename, boolean create) throws IOException
	{
		if(filename.startsWith("/"))
		{
			filename = filename.substring(1);
		}
		
		file = (FileConnection)Connector.open("file:///" + filename); //, create ? Connector.READ_WRITE : Connector.READ);

		if(file.isDirectory())
		{
			file.close();
			throw new IOException("Not a file: " + file.getPath() + file.getName());
		}
		else if(!file.exists())
		{
			if(create)
			{
				file.create();
			}
			else
			{
				file.close();
				throw new IOException("File does not exist: " + file.getPath() + file.getName());
			}
		}

		rais = new FileInputStream(file);

		if(rais.getCapacity() == 0)
		{
			entries = new Hashtable();
			finished = false;
		}
		else
		{
			checkFormat();
			finished = true;
		}

		files = new Hashtable();
	}

	public void init(RandomAccessInputStream rais) throws IOException
	{
		this.rais = rais;

		checkFormat();
		
		finished = true;
		files = new Hashtable();
	}
	
	protected void checkFormat() throws IOException
	{
	}

	public FileConnection getBaseConnection()
	{
		return file;
	}

	public FileConnection getFileConnection(String file, int mode) throws IOException
	{
		checkClosed();
		checkEntries();
		return new ArchiveConnection(this.file, this, file, mode);
	}

	protected void checkClosed()
	{
		if(closed)
		{
			throw new IllegalStateException("Container is closed");
		}
	}

	public boolean isReadOnly()
	{
		checkClosed();
		return file != null ? !file.canWrite() : true;
	}

	protected void checkReadOnly() throws IOException
	{
		if(isReadOnly())
		{
			throw new IOException("Container is read only");
		}
	}

	protected void checkEntries() throws IOException
	{
		if(entries == null)
		{
			readEntries();
		}
	}

	protected abstract void readEntries() throws IOException;

	public boolean addEntry(RandomAccessInputStream source, String name, long time, boolean replace, ProgressCallback callback) throws IOException
	{
		if(replace)
		{
			deleteEntry(getEntry(name));
		}
		else
		{
			checkReadOnly();
			checkEntries();

			if(getEntry(name) != null)
			{
				return false;
			}
		}

		if(callback != null)
		{
			callback.setText(Locale.getString(this, Locale.PACKING_FILE, name));
			callback.setProgress(0);
			callback.setMax(source.getCapacity());
		}

		if(time < 0)
		{
			time = System.currentTimeMillis();
		}

		entries.put(name, createActualEntry(source, name, time, callback));
		addFile(name);

		finished = false;

		return true;
	}

	public boolean deleteEntry(ArchiveEntry entry) throws IOException
	{
		if(entry == null)
		{
			return false;
		}

		checkReadOnly();
		checkEntries();

		if(entry.getName().endsWith("/"))
		{
			if(listFiles(entry.getName()).hasMoreElements())
			{
				return false;
			}
			else
			{
				deleteFile(entry.getName());
				return true;
			}
		}

		if(!entries.containsKey(entry.getName()))
		{
			return false;
		}

		if(!entry.pureVirtual)
		{
			deleteActualEntry(entry);
			finished = false;
		}

		entries.remove(entry.getName());
		deleteFile(entry.getName());

		return true;
	}

	public void renameDirectory(String oldName, String newName) throws IOException
	{
		checkReadOnly();
		checkEntries();

		Enumeration e = entries.elements();
		ArchiveEntry entry;
		String entryName;
		int index;

		index = oldName.length();

		while(e.hasMoreElements())
		{
			entry = (ArchiveEntry)e.nextElement();
			entryName = entry.getName();

			if(entryName.startsWith(oldName))
			{
				renameEntry(entry, newName + entryName.substring(index));
			}
		}

		Directory.deleteFile(files, oldName);
	}

	public boolean renameEntry(ArchiveEntry entry, String newName) throws IOException
	{
		String oldName = entry.getName();

		if(!entry.pureVirtual)
		{
			checkReadOnly();
			checkEntries();

			renameActualEntry(entry, newName);
			finished = false;
		}
		else
		{
			entry.setName(newName);
		}

		Directory.renameFile(files, null, oldName, newName);
		entries.remove(oldName);
		entries.put(newName, entry);

		return true;
	}

	protected abstract ArchiveEntry createActualEntry(RandomAccessInputStream source, String name, long time, ProgressCallback callback) throws IOException;
	protected abstract void deleteActualEntry(ArchiveEntry entry) throws IOException;
	protected abstract void renameActualEntry(ArchiveEntry entry, String newName) throws IOException;

	public void updateOffset(int base, int delta) throws IOException
	{
		Enumeration e = entries();
		ArchiveEntry entry;

		while(e.hasMoreElements())
		{
			entry = (ArchiveEntry)e.nextElement();

			if(entry.offset > base)
			{
				entry.offset += delta;
			}
		}
	}

	public abstract void finish() throws IOException;

	public void close() throws IOException
	{
		Enumeration e = entries.elements();

		while(e.hasMoreElements())
		{
			realizeEntry((ArchiveEntry)e.nextElement());
		}

		if(!finished)
		{
			finish();
		}

		ProgressBar.hide();

		closed = true;
		entries = null;
		files = null;

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

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

	public Enumeration entries() throws IOException
	{
		checkClosed();
		checkEntries();

		return entries.elements();
	}

	public ArchiveEntry getEntry(String name) throws IOException
	{
		if(name.endsWith("/"))
		{
			if(listFiles(name) != null)
			{
				return new ArchiveEntry(name);
			}
			else
			{
				return null;
			}
		}
		else
		{
			checkClosed();
			checkEntries();

			return (ArchiveEntry)entries.get(name);
		}
	}

	public abstract InputStream getInputStream(ArchiveEntry entry) throws IOException;

	public int size() throws IOException
	{
		checkClosed();
		checkEntries();

		return entries.size();
	}

	public boolean entryExists(String name) throws IOException
	{
		checkClosed();
		checkEntries();

		return entries.containsKey(name);
	}

	public long directorySize(String path, boolean includeSubDirs)
	{
		long size = 0;

		Enumeration e = listFiles(path);
		String s;

		while(e.hasMoreElements())
		{
			s = (String)e.nextElement();

			if(s.endsWith("/"))
			{
				if(includeSubDirs)
				{
					size += directorySize(path + s, true);
				}
			}
			else
			{
				size += ((ArchiveEntry)entries.get(path + s)).getSize();
			}
		}

		return size;
	}

	public void virtualizeEntry(ArchiveEntry entry) throws IOException
	{
		checkEntries();

		if(!entries.containsKey(entry.getName()))
		{
			checkReadOnly();
			entries.put(entry.getName(), entry);
		}

		if(entry.vfc == null)
		{
			if(virtualizeToRAM) // && !entry.pureVirtual)
			{
				try
				{
					entry.vfc = new ByteArrayFileConnection(file.getPath(), entry.pureVirtual ? -1 : entry.getSize(), 4096);
				}
				catch(Throwable t)
				{
				}
			}

			if(entry.vfc == null)
			{
				entry.vfc = (FileConnection)Connector.open("file:///" + Connector.createTempFile(file.getPath(), false));
				entry.vfc.create();
			}

			if(!entry.pureVirtual)
			{
				ProgressCallback callback = ProgressBar.getProgressCallback();
				ProgressBar.show();

				callback.setText(Locale.getString(this, Locale.UNPACKING_FILE, entry.getName()));

				byte[] buf = new byte[AuxClass.COPYBUFSIZE];
				int len, total;

				InputStream is = getInputStream(entry);
				OutputStream os = entry.vfc.openOutputStream();

				total = 0;
				callback.setMax(is.available());

				while((len = is.read(buf)) > 0)
				{
					os.write(buf, 0, len);
					total += len;

					if(callback.getMax() < total)
					{
						callback.setMax(-1);
					}
					
					callback.setProgress(total);
				}

				os.close();
				is.close();

				ProgressBar.hide();
			}
		}
	}

	public void realizeEntry(ArchiveEntry entry) throws IOException
	{
		if(entry.vfc != null)
		{
			checkReadOnly();
			checkEntries();

			ProgressCallback callback = ProgressBar.getProgressCallback();
			ProgressBar.show();

			deleteEntry(entry);
			addEntry(new FileInputStream(entry.vfc), entry.getName(), -1, false, callback);

			Connector.releaseTempFile(entry.vfc.getPath() + entry.vfc.getName());

			entry.vfc.delete();
			entry.vfc.close();
		}
	}

	public void addFile(String file)
	{
		Directory.addFile(files, file);
	}

	public void deleteFile(String file)
	{
		Directory.deleteFile(files, file);
	}

	public Enumeration listFiles(String path)
	{
		return Directory.listFiles(files, path);
	}

	public boolean fileExists(String file)
	{
		return Directory.fileExists(files, file);
	}
}