package com.one;

import java.io.*;
import java.util.Vector;

/**
 * Поток с возможностью замены произвольного участка.
 */
public class ReplaceableInputStream extends RandomAccessInputStream
{
	protected RandomAccessInputStream is;
	protected int currpos, markedpos;
	
	protected int repstart;  // начало заменяемого фрагмента
	protected int repend;    // конец заменяемого фрагмента
	protected int replen;    // длина заменяющего фрагмента
	protected int delta;     // изменение размера потока
	protected int repbufend; // позиция, на которой заканчивается замена
	
	protected RandomAccessInputStream replace;
	
	public ReplaceableInputStream(RandomAccessInputStream is)
	{
		this.is = is;
		
		markedpos = currpos = 0;
		
		removeReplace();
	}

	/**
	 * Сложить в вектор цепочку из потоков замены.
	 * Предполагается, что в цепочке есть один базовый поток
	 * и пучок вложенных RIS'ов.
	 *
	 * @param v вектор, в котором будет собрана цепочка
	 */
	public void traverseReplaceChain(Vector v)
	{
		if(is instanceof ReplaceableInputStream)
		{
			((ReplaceableInputStream)is).traverseReplaceChain(v);
		}

		v.addElement(this);
	}

	/**
	 * Получить базовый для всей цепочки RIS'ов поток.
	 * @return поток, на котором основан первый RIS в цепочке
	 */
	public RandomAccessInputStream getBaseStream()
	{
		if(is instanceof ReplaceableInputStream)
		{
			return ((ReplaceableInputStream)is).getBaseStream();
		}

		return is;
	}

	/**
	 * Установка замены.
	 * 
	 * Данные в потоке со Start включая по End не включая
	 * заменяется на данные из потока Replace.
	 * Размер замещающего потока может не совпадать
	 * с размером замещаемого участка, в этом случае
	 * соответственно изменяется размер всего потока.
	 *
	 * @param replace замещающий поток
	 * @param start начало замещаемого участка
	 * @param end конец замещаемого участка
	 * @return на сколько изменилась длина потока
	 */
	public int setReplace(RandomAccessInputStream replace, int start, int end) throws IOException
	{
		this.replace = replace;

		repstart = start;
		repend = end;
		replen = replace.getCapacity();

		repbufend = repstart + replen;
		delta = replen - repend + repstart;

		return delta;
	}

	/**
	 * Тоже установка замены, только на основе массива байт.
	 * Для создания замещающего потока используется BufferedInputStream.
	 *
	 * @param b замещающий массив
	 * @param start начало замещаемого участка
	 * @param end конец замещаемого участка
	 * @return на сколько изменилась длина потока
	 */
	public int setReplace(byte[] b, int start, int end) throws IOException
	{
		return setReplace(new BufferedInputStream(b), start, end);
	}
	
	/**
	 * Отмена замены.
	 */
	public void removeReplace()
	{
		replace = null;
		System.gc();
		
		repstart = repend = replen = repbufend = -1;
		delta = 0;
	}

	/**
	 * Сколько операций записи потребуется произвести,
	 * чтобы сохранить изменения в файл.
	 * Учитывается как размер самой замены, так и операции
	 * по перемещению хвоста файла после замены.
	 */
	public int getWriteCount() throws IOException
	{
		int res;

		if(is instanceof ReplaceableInputStream)
		{
			res = ((ReplaceableInputStream)is).getWriteCount();
		}
		else
		{
			res = 0;
		}

		if(delta > 0)
		{
			res += delta + (is.getCapacity() - repend) + replen;
		}
		else if(delta < 0)
		{
			res += (is.getCapacity() - repend) + replen;
		}
		else
		{
			res += replen;
		}

		return res;
	}

	public RandomAccessInputStream getReplaceStream()
	{
		return replace;
	}
	
	public byte[] getReplace() throws IOException
	{
		if(replace instanceof BufferedInputStream)
		{
			return ((BufferedInputStream)replace).getBuffer();
		}
		else
		{
			replace.setPosition(0);
			return (new BufferedInputStream(replace, true, false)).getBuffer();
		}
	}
	
	public int getReplaceStart()
	{
		return repstart;
	}
	
	public int getReplaceEnd()
	{
		return repend;
	}
	
	public boolean isReplaceSet()
	{
		return replen >= 0;
	}
	
	public int available() throws IOException
	{
		return getCapacity() - currpos;
	}
	
	/**
	 * Освобожение ресурсов, используемых этим потоком.
	 * Базовый поток не закрывается для дальнейшего использования.
	 */
	public void close()
	{
		removeReplace();
	}
	
	public void mark(int readLimit)
	{
		markedpos = currpos;
	}
	
	public int read() throws IOException
	{
		if(replen < 0 || currpos < repstart)
		{
			is.setPosition(currpos);
			currpos++;
			
			return is.read();
		}
		else if(currpos < repbufend)
		{
			replace.setPosition(currpos++ - repstart);
			return replace.read();
		}
		else
		{
			is.setPosition(currpos - delta);
			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
	{
		int read = 0;
		
		if(replen < 0)
		{
			is.setPosition(currpos);
			
			read = is.read(b, off, len);
			
			currpos += read;
			
			return read;
		}
		
		int available = available();

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

		if(len > available)
		{
			len = available;
		}
		
		int temp;
		
		while(len > 0)
		{
			if(currpos < repstart)
			{
				temp = repstart - currpos;
				
				if(temp > len)
				{
					temp = len;
				}

				is.setPosition(currpos);
				temp = is.read(b, off, temp);
			}
			else if(currpos < repbufend)
			{
				temp = repbufend - currpos;
				
				if(temp > len)
				{
					temp = len;
				}
				
				replace.setPosition(currpos - repstart);
				temp = replace.read(b, off, temp);
			}
			else
			{
				is.setPosition(currpos - delta);
				temp = is.read(b, off, len);
			}

			currpos += temp;
			read += temp;
			off += temp;
			len -= temp;
		}
		
		return read;
	}
	
	public void reset()
	{
		currpos = markedpos;
	}
	
	public long skip(long n) throws IOException
	{
		if(n > available())
		{
			n = available();
		}
		
		currpos += n;
		
		return n;
	}
	
	public void setPosition(int pos) throws IOException
	{
		if(pos == currpos)
		{
			return;
		}
		
		if(pos < 0)
		{
			pos = 0;
		}
		else if(pos > getCapacity())
		{
			pos = getCapacity();
		}
		
		currpos = pos;
	}
	
	public int getPosition()
	{
		return currpos;
	}
	
	/**
	 * Получение емкости потока.
	 * Возвращаемое значение может изменяться при установке замены.
	 */
	public int getCapacity() throws IOException
	{
		return is.getCapacity() + delta;
	}
	
	public void update() throws IOException
	{
		is.update();
	}

	public void pause() throws IOException
	{
		is.pause();
	}

	public void resume() throws IOException
	{
		is.resume();
	}
	
//	private static void out(String s)
//	{
//		System.out.println("[ReplaceableInputStream] " + s);
//	}
}
