package net.minecraft.src;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.InflaterInputStream;

public class RegionFile {
	private static final byte[] emptySector = new byte[4096];
	private final File fileName;
	private RandomAccessFile dataFile;
	private final int[] offsets = new int[1024];
	private final int[] chunkTimestamps = new int[1024];
	private ArrayList sectorFree;

	/** McRegion sizeDelta */
	private int sizeDelta;
	private long lastModified = 0L;

	public RegionFile(File par1File) {
		this.fileName = par1File;
		this.sizeDelta = 0;

		try {
			if (par1File.exists()) {
				this.lastModified = par1File.lastModified();
			}

			this.dataFile = new RandomAccessFile(par1File, "rw");
			int var2;

			if (this.dataFile.length() < 4096L) {
				for (var2 = 0; var2 < 1024; ++var2) {
					this.dataFile.writeInt(0);
				}

				for (var2 = 0; var2 < 1024; ++var2) {
					this.dataFile.writeInt(0);
				}

				this.sizeDelta += 8192;
			}

			if ((this.dataFile.length() & 4095L) != 0L) {
				for (var2 = 0; (long) var2 < (this.dataFile.length() & 4095L); ++var2) {
					this.dataFile.write(0);
				}
			}

			var2 = (int) this.dataFile.length() / 4096;
			this.sectorFree = new ArrayList(var2);
			int var3;

			for (var3 = 0; var3 < var2; ++var3) {
				this.sectorFree.add(Boolean.valueOf(true));
			}

			this.sectorFree.set(0, Boolean.valueOf(false));
			this.sectorFree.set(1, Boolean.valueOf(false));
			this.dataFile.seek(0L);
			int var4;

			for (var3 = 0; var3 < 1024; ++var3) {
				var4 = this.dataFile.readInt();
				this.offsets[var3] = var4;

				if (var4 != 0 && (var4 >> 8) + (var4 & 255) <= this.sectorFree.size()) {
					for (int var5 = 0; var5 < (var4 & 255); ++var5) {
						this.sectorFree.set((var4 >> 8) + var5, Boolean.valueOf(false));
					}
				}
			}

			for (var3 = 0; var3 < 1024; ++var3) {
				var4 = this.dataFile.readInt();
				this.chunkTimestamps[var3] = var4;
			}
		} catch (IOException var6) {
			var6.printStackTrace();
		}
	}

	/**
	 * args: x, y - get uncompressed chunk stream from the region file
	 */
	public synchronized DataInputStream getChunkDataInputStream(int par1, int par2) {
		if (this.outOfBounds(par1, par2)) {
			return null;
		} else {
			try {
				int var3 = this.getOffset(par1, par2);

				if (var3 == 0) {
					return null;
				} else {
					int var4 = var3 >> 8;
					int var5 = var3 & 255;

					if (var4 + var5 > this.sectorFree.size()) {
						return null;
					} else {
						this.dataFile.seek((long) (var4 * 4096));
						int var6 = this.dataFile.readInt();

						if (var6 > 4096 * var5) {
							return null;
						} else if (var6 <= 0) {
							return null;
						} else {
							byte var7 = this.dataFile.readByte();
							byte[] var8;

							if (var7 == 1) {
								var8 = new byte[var6 - 1];
								this.dataFile.read(var8);
								return new DataInputStream(
										new BufferedInputStream(new GZIPInputStream(new ByteArrayInputStream(var8))));
							} else if (var7 == 2) {
								var8 = new byte[var6 - 1];
								this.dataFile.read(var8);
								return new DataInputStream(new BufferedInputStream(
										new InflaterInputStream(new ByteArrayInputStream(var8))));
							} else {
								return null;
							}
						}
					}
				}
			} catch (IOException var9) {
				return null;
			}
		}
	}

	/**
	 * args: x, z - get an output stream used to write chunk data, data is on disk
	 * when the returned stream is closed
	 */
	public DataOutputStream getChunkDataOutputStream(int par1, int par2) {
		return this.outOfBounds(par1, par2) ? null
				: new DataOutputStream(new DeflaterOutputStream(new RegionFileChunkBuffer(this, par1, par2)));
	}

	/**
	 * args: x, z, data, length - write chunk data at (x, z) to disk
	 */
	protected synchronized void write(int par1, int par2, byte[] par3ArrayOfByte, int par4) {
		try {
			int var5 = this.getOffset(par1, par2);
			int var6 = var5 >> 8;
			int var7 = var5 & 255;
			int var8 = (par4 + 5) / 4096 + 1;

			if (var8 >= 256) {
				return;
			}

			if (var6 != 0 && var7 == var8) {
				this.write(var6, par3ArrayOfByte, par4);
			} else {
				int var9;

				for (var9 = 0; var9 < var7; ++var9) {
					this.sectorFree.set(var6 + var9, Boolean.valueOf(true));
				}

				var9 = this.sectorFree.indexOf(Boolean.valueOf(true));
				int var10 = 0;
				int var11;

				if (var9 != -1) {
					for (var11 = var9; var11 < this.sectorFree.size(); ++var11) {
						if (var10 != 0) {
							if (((Boolean) this.sectorFree.get(var11)).booleanValue()) {
								++var10;
							} else {
								var10 = 0;
							}
						} else if (((Boolean) this.sectorFree.get(var11)).booleanValue()) {
							var9 = var11;
							var10 = 1;
						}

						if (var10 >= var8) {
							break;
						}
					}
				}

				if (var10 >= var8) {
					var6 = var9;
					this.setOffset(par1, par2, var9 << 8 | var8);

					for (var11 = 0; var11 < var8; ++var11) {
						this.sectorFree.set(var6 + var11, Boolean.valueOf(false));
					}

					this.write(var6, par3ArrayOfByte, par4);
				} else {
					this.dataFile.seek(this.dataFile.length());
					var6 = this.sectorFree.size();

					for (var11 = 0; var11 < var8; ++var11) {
						this.dataFile.write(emptySector);
						this.sectorFree.add(Boolean.valueOf(false));
					}

					this.sizeDelta += 4096 * var8;
					this.write(var6, par3ArrayOfByte, par4);
					this.setOffset(par1, par2, var6 << 8 | var8);
				}
			}

			this.setChunkTimestamp(par1, par2, (int) (System.currentTimeMillis() / 1000L));
		} catch (IOException var12) {
			var12.printStackTrace();
		}
	}

	/**
	 * args: sectorNumber, data, length - write the chunk data to this RegionFile
	 */
	private void write(int par1, byte[] par2ArrayOfByte, int par3) throws IOException {
		this.dataFile.seek((long) (par1 * 4096));
		this.dataFile.writeInt(par3 + 1);
		this.dataFile.writeByte(2);
		this.dataFile.write(par2ArrayOfByte, 0, par3);
	}

	/**
	 * args: x, z - check region bounds
	 */
	private boolean outOfBounds(int par1, int par2) {
		return par1 < 0 || par1 >= 32 || par2 < 0 || par2 >= 32;
	}

	/**
	 * args: x, y - get chunk's offset in region file
	 */
	private int getOffset(int par1, int par2) {
		return this.offsets[par1 + par2 * 32];
	}

	/**
	 * args: x, z, - true if chunk has been saved / converted
	 */
	public boolean isChunkSaved(int par1, int par2) {
		return this.getOffset(par1, par2) != 0;
	}

	/**
	 * args: x, z, offset - sets the chunk's offset in the region file
	 */
	private void setOffset(int par1, int par2, int par3) throws IOException {
		this.offsets[par1 + par2 * 32] = par3;
		this.dataFile.seek((long) ((par1 + par2 * 32) * 4));
		this.dataFile.writeInt(par3);
	}

	/**
	 * args: x, z, timestamp - sets the chunk's write timestamp
	 */
	private void setChunkTimestamp(int par1, int par2, int par3) throws IOException {
		this.chunkTimestamps[par1 + par2 * 32] = par3;
		this.dataFile.seek((long) (4096 + (par1 + par2 * 32) * 4));
		this.dataFile.writeInt(par3);
	}

	/**
	 * close this RegionFile and prevent further writes
	 */
	public void close() throws IOException {
		if (this.dataFile != null) {
			this.dataFile.close();
		}
	}
}