package com.one;

import java.io.*;
import com.one.file.*;

/**
 * Файловый входной поток.
 * Призван работать там, где требуется поддержка mark / reset, но ее почему - то нет.
 */

/*
 * ЗАМЕЧАНИЕ
 *
 * В некоторых местах написано что-то вроде
 *
 * is.skip(n);
 * currpos += n;
 *
 * хотя, возможно, было бы правильнее написать
 *
 * n = is.skip(n);
 * currpos += n;
 *
 * Однако выяснилось, что skip() не всегда возвращает
 * именно количество пропущенных байт.
 * Видимо, поведение этого метода, а возможно, и других,
 * зависит от платформы (например, на телефонах SonyEricsson
 * этот метод возвращает текущую позицию в потоке).
 * Для исключения подобных ошибок в данном классе
 * значения, возвращаемые этими методами, не используются.
 * Вместо этого осуществляется явная проверка
 * количества доступных для считывания байт.
 */
public class FileInputStream extends RandomAccessInputStream
{
	protected FileConnection fc;
	protected InputStream is;
	protected int currpos;      // позиция первого несчитанного байта
	protected int markedpos;    // маркированная позиция
	protected boolean marksupp; // поддержка mark / reset базовым потоком

	public FileInputStream(FileConnection fc) throws IOException
	{
		setFileConnection(fc);
		
		currpos = 0;
		markedpos = 0;
	}
	
	public void setFileConnection(FileConnection fc) throws IOException
	{
		close();

		this.fc = fc;

		resume();
	}
	
	public FileConnection getBaseConnection()
	{
		return fc;
	}

	public void checkClosed() throws IOException
	{
		if(fc == null)
		{
			throw new IOException("Stream is closed");
		}
	}

	public void checkState() throws IOException
	{
		checkClosed();

		if(is == null)
		{
			throw new IOException("Stream is paused");
		}
	}
	
	public int available() throws IOException
	{
		checkState();
		return (int)(fc.fileSize() - currpos); // is.available();
	}
	
	public void close() throws IOException
	{
		pause();
		fc = null;
	}
	
	public void mark(int readLimit)
	{
		markedpos = currpos;
	}
	
	public int read() throws IOException
	{
		checkState();

		if(available() <= 0) // конец потока
		{
			return -1;
		}
		
		currpos++;
		
		return is.read();
	}
	
	public int read(byte[] b) throws IOException
	{
		return read(b, 0, b.length);
	}
	
	public int read(byte[] b, int off, int len) throws IOException
	{
		checkState();

		int available = available();

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

		if(len > available)
		{
			len = available;
		}
		
		is.read(b, off, len);
		
		currpos += len;
		
		return len;
	}
	
	public void reset() throws IOException
	{
		checkState();
		setPosition(markedpos);
	}
	
	public long skip(long n) throws IOException
	{
		checkState();

		if(n > available())
		{
			n = available();
		}
		
		is.skip(n);
		
		currpos += n;
		
		return n;
	}
	
	public void setPosition(int pos) throws IOException
	{
		checkState();

		if(pos == currpos)
		{
			return;
		}
		
		if(pos < 0)
		{
			pos = 0;
		}
		else if(pos > getCapacity())
		{
			pos = getCapacity();
		}

		//out("Changing position from " + currpos + " to " + pos + "...");
		
		if(pos < currpos)
		{
			if(marksupp)
			{
				//out("Mark supported, resetting...");
				is.reset();
			}
			else
			{
				//out("Mark NOT supported, reopening stream...");
				is.close();
				is = fc.openInputStream();
			}

			//out("Skipping " + pos + " bytes, actually skipped " + is.skip(pos) + " bytes");
			is.skip(pos);
		}
		else
		{
			//out("Skipping " + (pos - currpos) + " bytes, actually skipped " + is.skip(pos - currpos) + " bytes");
			is.skip(pos - currpos);
		}
		
		currpos = pos;
	}
	
	public int getPosition()
	{
		return currpos;
	}
	
	public int getCapacity() throws IOException
	{
		checkState();
		return (int)fc.fileSize(); // is.available() + currpos;
	}
	
	public void update() throws IOException
	{
		checkState();
		
		int pos = currpos;
		currpos = 0;
		
		is.close();
		is = fc.openInputStream();
		
		if(marksupp = is.markSupported())
		{
			is.mark(available() + 0x100);
		}
		
		setPosition(pos);
	}

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

	public void resume() throws IOException
	{
		checkClosed();

		int pos = currpos;
		currpos = 0;
		
		is = fc.openInputStream();

		if(marksupp = is.markSupported())
		{
			is.mark(available() + 0x100);
		}

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