package org.apache.commons.compress.archivers.tar;

import com.one.ArchiveContainer;
import com.one.ArchiveEntry;
import com.one.FileInputStream;
import com.one.PartialInputStream;
import com.one.ProgressBar;
import com.one.RandomAccessInputStream;
import com.one.ReplaceableInputStream;
import com.vmx.AuxClass;
import com.vmx.Locale;
import com.vmx.ProgressCallback;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import org.apache.commons.compress.utils.ArchiveUtils;

public class TarArchive extends ArchiveContainer
{
	public static final int RECORD_SIZE = 512;

	protected int tailOffset;

	protected void readEntries() throws IOException
	{
		entries = new Hashtable();
		TarEntry entry;

		while(true)
		{
			tailOffset = rais.getPosition();

			entry = TarEntry.readEntryHeader(rais);

			if(entry == null)
			{
				break;
			}

			entries.put(entry.getName(), entry);
			addFile(entry.getName());

			rais.skip(entry.getRecordCount() * RECORD_SIZE);
		}
	}

	public InputStream getInputStream(ArchiveEntry entry) throws IOException
	{
		if(entry == null)
		{
			return null;
		}

		checkClosed();

		return new PartialInputStream(rais, entry.offset, entry.getSize());
	}

	protected ArchiveEntry createActualEntry(RandomAccessInputStream source, String name, long time, ProgressCallback callback) throws IOException
	{
		TarEntry entry = new TarEntry(name);
		entry.setSize(source != null ? source.getCapacity() : 0);
		entry.setModTime(time);

		//int offset = finished ? (int)(file.fileSize() - RECORD_SIZE * 2) : (int)file.fileSize();

		file.truncate(tailOffset);
		OutputStream os = file.openOutputStream(tailOffset);

		entry.writeEntryHeader(os);

		entry.headerOffset = tailOffset;
		entry.offset = (int)file.fileSize();

		if(source != null)
		{
			byte[] buf = new byte[AuxClass.COPYBUFSIZE];
			int len;

			if(callback != null)
			{
				while(source.available() > 0)
				{
					len = source.read(buf);
					os.write(buf, 0, len);

					callback.progress(len);
				}
			}
			else
			{
				while(source.available() > 0)
				{
					len = source.read(buf);
					os.write(buf, 0, len);
				}
			}

			source.close();
		}

		int left = RECORD_SIZE - entry.getSize() % RECORD_SIZE;

		if(left < RECORD_SIZE)
		{
			os.write(new byte[left], 0, left);
		}

		tailOffset = (int)file.fileSize();
		
		os.close();

		rais.update();

		return entry;
	}

	protected void deleteActualEntry(ArchiveEntry entry) throws IOException
	{
		TarEntry tarentry = (TarEntry)entry;

		if(finished)
		{
			rais.pause();
			file.truncate(tailOffset); // file.fileSize() - RECORD_SIZE * 2);
			rais.resume();
		}

		rais = new ReplaceableInputStream(rais);

		int delta = ((ReplaceableInputStream)rais).setReplace(new byte[0], tarentry.headerOffset, tarentry.offset + tarentry.getCompressedSize());

		updateOffset(tarentry.headerOffset, delta);
	}

	protected void renameActualEntry(ArchiveEntry entry, String newName) throws IOException
	{
		TarEntry tarentry = (TarEntry)entry;

		ByteArrayOutputStream baos = new ByteArrayOutputStream();

		tarentry.setName(newName);
		tarentry.writeEntryHeader(baos);

		byte[] b = baos.toByteArray();
		baos.close();

		if(finished)
		{
			rais.pause();
			file.truncate(file.fileSize() - RECORD_SIZE * 2);
			rais.resume();
		}

		rais = new ReplaceableInputStream(rais);

		int delta = ((ReplaceableInputStream)rais).setReplace(b, tarentry.headerOffset, tarentry.offset);

		updateOffset(tarentry.headerOffset, delta);
	}

	public void updateOffset(int base, int delta) throws IOException
	{
		if(finished)
		{
			return;
		}

		checkReadOnly();
		checkEntries();

		Enumeration e = entries();
		ArchiveEntry entry;
		TarEntry tarentry;

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

			if(entry instanceof TarEntry)
			{
				tarentry = (TarEntry)entry;

				if(tarentry.headerOffset > base)
				{
					tarentry.headerOffset += delta;
				}
			}

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

	public void finish() throws IOException
	{
		if(finished)
		{
			return;
		}

		checkReadOnly();

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

		callback.setText(Locale.getString(super.getClass().getName(), Locale.WRITING_FILE, file.getName()));

		if(rais instanceof ReplaceableInputStream)
		{
			file = AuxClass.updateFileData(file, (ReplaceableInputStream)rais, callback);
		}
		else
		{
			rais.close();
		}

		OutputStream os = file.openOutputStream(file.fileSize());

		byte[] buf = new byte[RECORD_SIZE];

		os.write(buf);
		os.write(buf);

		os.close();

		finished = true;

		rais = new FileInputStream(file);

		ProgressBar.hide();
	}

	/**
	 * Checks if the signature matches what is expected for a tar file.
	 *
	 * @param signature
	 *            the bytes to check
	 * @param length
	 *            the number of bytes to check
	 * @return true, if this stream is a tar archive stream, false otherwise
	 */
	public static boolean matches(byte[] signature, int length)
	{
		if(length < TarConstants.VERSION_OFFSET + TarConstants.VERSIONLEN)
		{
			return false;
		}

		if(ArchiveUtils.matchAsciiBuffer(TarConstants.MAGIC_POSIX,
			signature, TarConstants.MAGIC_OFFSET, TarConstants.MAGICLEN)
			&& ArchiveUtils.matchAsciiBuffer(TarConstants.VERSION_POSIX,
			signature, TarConstants.VERSION_OFFSET, TarConstants.VERSIONLEN))
		{
			return true;
		}

		if(ArchiveUtils.matchAsciiBuffer(TarConstants.MAGIC_GNU,
			signature, TarConstants.MAGIC_OFFSET, TarConstants.MAGICLEN)
			&& (ArchiveUtils.matchAsciiBuffer(TarConstants.VERSION_GNU_SPACE,
			signature, TarConstants.VERSION_OFFSET, TarConstants.VERSIONLEN)
			|| ArchiveUtils.matchAsciiBuffer(TarConstants.VERSION_GNU_ZERO,
			signature, TarConstants.VERSION_OFFSET, TarConstants.VERSIONLEN)))
		{
			return true;
		}

		// COMPRESS-107 - recognise Ant tar files
		if(ArchiveUtils.matchAsciiBuffer(TarConstants.MAGIC_ANT,
			signature, TarConstants.MAGIC_OFFSET, TarConstants.MAGICLEN)
			&& ArchiveUtils.matchAsciiBuffer(TarConstants.VERSION_ANT,
			signature, TarConstants.VERSION_OFFSET, TarConstants.VERSIONLEN))
		{
			return true;
		}

		return false;
	}
}
