/*
	Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
	и других спецификаций для функционирования компактных приложений на языке
	Java (мидлетов) в среде программного обеспечения Малик Эмулятор.

	Copyright © 2016, 2019 Малик Разработчик

	Это свободная программа: вы можете перераспространять ее и/или изменять
	ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
	в каком она была опубликована Фондом свободного программного обеспечения;
	либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

	Эта программа распространяется в надежде, что она будет полезной,
	но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
	или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
	общественной лицензии GNU.

	Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
	вместе с этой программой. Если это не так, см.
	<https://www.gnu.org/licenses/>.
*/


package malik.emulator.io.cloud;

import java.io.*;
import malik.emulator.util.*;

public class FileInputStream extends InputStream
{
	private static final int OPEN_ERROR_NO_ERROR = 0;
	private static final int OPEN_ERROR_FILE_NAME_NOT_SPECIFIED = 1;
	private static final int OPEN_ERROR_FILE_ACCESS_DENIED = 2;


	private int handle;
	private int openError;
	private long position;
	private long size;
	private Object lock;
	private String fileName;
	private DataDescriptor descriptor;

	public FileInputStream(String fileName)
	{
		int h;
		char[] fn;
		if(fileName == null || (h = fileName.length()) == 0)
		{
			this.handle = 0;
			this.openError = OPEN_ERROR_FILE_NAME_NOT_SPECIFIED;
			this.position = 0L;
			this.size = 0L;
			this.lock = new Object();
			this.fileName = fileName;
			this.descriptor = null;
			return;
		}
		fileName.getChars(0, h, fn = new char[h + 1], 0);
		if((h = (int) MalikSystem.syscall(Array.getFirstElementAddress(fn), 1, 0x0010)) == 0)
		{
			this.handle = 0;
			this.openError = OPEN_ERROR_FILE_ACCESS_DENIED;
			this.position = 0L;
			this.size = 0L;
			this.lock = new Object();
			this.fileName = fileName;
			this.descriptor = null;
			return;
		}
		this.handle = h;
		this.openError = OPEN_ERROR_NO_ERROR;
		this.position = 0L;
		this.size = MalikSystem.syscall((long) h, 0x0015);
		this.lock = new Object();
		this.fileName = fileName;
		this.descriptor = new DataDescriptor();
	}

	public String toString()
	{
		return (handle != 0 ? "Открытый для чтения файл " : "Закрытый файл ").concat(fileName);
	}

	public void checkOpenError()
			throws IOException
	{
		switch(openError)
		{
		default:
			if(handle != 0)
			{
				return;
			}
			throw new IOException("FileInputStream: " +
					"файл " + fileName + " был закрыт.");
		case OPEN_ERROR_FILE_NAME_NOT_SPECIFIED:
			throw new IOException("FileInputStream: " +
					"имя файла не задано.");
		case OPEN_ERROR_FILE_ACCESS_DENIED:
			throw new IOException("FileInputStream: " +
					"файл " + fileName + " недоступен для чтения.");
		}
	}

	public boolean hasOpenError()
	{
		return openError != OPEN_ERROR_NO_ERROR || handle == 0;
	}

	public final void close()
			throws IOException
	{
		int h;
		int error = 0;
		synchronized(lock)
		{
			label0:
			{
				if((h = handle) == 0)
				{
					error = 1;
					break label0;
				}
				MalikSystem.syscall((long) h, 0x0011);
				handle = 0;
			}
		}
		if(error == 1)
		{
			throw new IOException("FileInputStream: " +
					"файл " + fileName + " был закрыт.");
		}
	}

	public final void reset()
			throws IOException
	{
		int h;
		long z;
		synchronized(lock)
		{
			checkOpenError();
			h = handle;
			z = 0L;
			z = position - MalikSystem.syscall(h, MalikSystem.getLocalVariableAddress(z), 0x0014);
			MalikSystem.syscall(h, MalikSystem.getLocalVariableAddress(z), 0x0014);
		}
	}

	public final void mark(int readLimit)
	{
		int h;
		long z;
		synchronized(lock)
		{
			if((h = handle) != 0)
			{
				z = 0L;
				position = MalikSystem.syscall(h, MalikSystem.getLocalVariableAddress(z), 0x0014);
			}
		}
	}

	public final boolean markSupported()
	{
		return handle != 0;
	}

	public final int read()
			throws IOException
	{
		int result;
		synchronized(lock)
		{
			checkOpenError();
			result = 0;
			descriptor.setDataInfo(MalikSystem.getLocalVariableAddress(result), 1);
			if(readFile() != 1)
			{
				result = -1;
			}
		}
		return result;
	}

	public final int read(byte[] dst)
			throws IOException
	{
		int result;
		int length;
		if(dst == null)
		{
			throw new NullPointerException("FileInputStream.read: " +
					"параметр dst равен нулевой ссылке.");
		}
		synchronized(lock)
		{
			checkOpenError();
			label0:
			{
				if((length = dst.length) <= 0)
				{
					result = 0;
					break label0;
				}
				descriptor.setDataInfo(dst, 0, length);
				result = readFile();
			}
		}
		return result;
	}

	public final int read(byte[] dst, int offset, int length)
			throws IOException
	{
		int result;
		int lim;
		int len;
		if(dst == null)
		{
			throw new NullPointerException("FileInputStream.read: " +
					"параметр dst равен нулевой ссылке.");
		}
		if((lim = offset + length) > (len = dst.length) ||
				lim < offset || offset > len || offset < 0)
		{
			throw new ArrayIndexOutOfBoundsException("FileInputStream.read: " +
					"индекс выходит из диапазона.");
		}
		synchronized(lock)
		{
			checkOpenError();
			label0:
			{
				if(length <= 0)
				{
					result = 0;
					break label0;
				}
				descriptor.setDataInfo(dst, offset, length);
				result = readFile();
			}
		}
		return result;
	}

	public final int available()
			throws IOException
	{
		int result;
		long z;
		synchronized(lock)
		{
			checkOpenError();
			z = 0L;
			result = (int) Math.min((long) Integer.MAX_VALUE, size -
					MalikSystem.syscall(handle, MalikSystem.getLocalVariableAddress(z), 0x0014));
		}
		return result;
	}

	public final long skip(long bytesCount)
			throws IOException
	{
		int h;
		long z;
		long result;
		synchronized(lock)
		{
			checkOpenError();
			result = 0L;
			if(bytesCount > 0L)
			{
				h = handle;
				z = 0L;
				result = -MalikSystem.syscall(h, MalikSystem.getLocalVariableAddress(z), 0x0014) +
						MalikSystem.syscall(h, MalikSystem.getLocalVariableAddress(bytesCount),
						0x0014);
			}
		}
		return result;
	}

	public final long seek(long offset)
			throws IOException
	{
		long result;
		synchronized(lock)
		{
			checkOpenError();
			result = MalikSystem.syscall(handle,
					MalikSystem.getLocalVariableAddress(offset), 0x0014);
		}
		return result;
	}

	public final long getSize()
	{
		return size;
	}

	public final String getFileName()
	{
		return fileName;
	}

	protected void $finalize$()
	{
		int h;
		if((h = handle) != 0)
		{
			MalikSystem.syscall((long) h, 0x0011);
			handle = 0;
		}
	}

	private int readFile()
			throws InterruptedIOException
	{
		int remaining;
		int readed;
		int length;
		int h = handle;
		long z = 0L;
		DataDescriptor d = descriptor;
		remaining = (int) Math.min((long) Integer.MAX_VALUE,
				size - MalikSystem.syscall(h, MalikSystem.getLocalVariableAddress(z), 0x0014));
		readed = (int) MalikSystem.syscall(h, d.getDescriptorAddress(), 0x0012);
		length = d.getLength();
		if(readed != remaining && readed != length)
		{
			throw new InterruptedIOException("FileInputStream.read: " + 
					"процесс чтения данных из файла " + fileName +
					" был прерван. Прочитано байт: " + readed +
					" из " + Math.min(remaining, length) + ".", readed);
		}
		return remaining == 0 ? -1 : readed;
	}
}
