package com.vmx;

import com.classpath.util.Characters;
import java.io.*;
import com.one.*;
import com.one.file.FileConnection;

public class InputStreamDecoder implements CharacterIterator
{
	private BufDataInputStream bdis;
	private int enc, offset;

	/**
	 * Конструктор
	 */
	public InputStreamDecoder(BufDataInputStream bdis, int enc, int offset)
	{
		this.bdis = bdis;

		this.enc = enc;
		this.offset = offset;
	}
	
	/**
	 * Создать InputStreamDecoder для декодирования ресурса
	 */
	public static InputStreamDecoder getResourceDecoder(String resname) throws IOException
	{
		return getStreamDecoder(AuxClass.getResourceAsStream(resname));
	}

	/**
	 * Создать InputStreamDecoder для декодирования потока
	 */
	public static InputStreamDecoder getStreamDecoder(InputStream iis) throws IOException
	{
		return getDecoder(new BufferedInputStream(iis), StringEncoder.ENC_DEFAULT, true);
	}

	/**
	 * Создать InputStreamDecoder для декодирования файла
	 */
	public static InputStreamDecoder getFileDecoder(FileConnection fc) throws IOException
	{
		return getDecoder(new FileInputStream(fc), StringEncoder.ENC_DEFAULT, true);
	}

	/**
	 * Общий случай
	 */
	public static InputStreamDecoder getDecoder(RandomAccessInputStream is, int encoding, boolean detect) throws IOException
	{
		is.setPosition(0);

		if(detect)
		{
			encoding = detectEncoding(is, encoding);
		}

		int offset = is.getPosition();

		if(offset > 0)
		{
			is.setPosition(0);
		}

		return new InputStreamDecoder(new BufDataInputStream(is), encoding, offset);
	}

	/**
	 * Осознать, в какой кодировке is.
	 * Если в UTF (есть BOM сигнатура), вернуть ENC_UTFx.
	 * Если нет, то вернуть переданную кодировку (по умолчанию).
	 *
	 * В качестве побочного эффекта:
	 * если в потоке действительно есть сигнатура, то поток не сбрасывается,
	 * и из него можно сразу читать нормальный символ.
	 */	
	public static int detectEncoding(RandomAccessInputStream is, int defenc) throws IOException
	{
		is.mark(4);
		
		/*
		 * BOM сигнатура представляет собой символ
		 * U+FEFF (неразрывный пробел с нулевой шириной),
		 * записаный тем или иным образом.
		 * Соответственно здесь проверяется наличие
		 * в начале потока этого символа в формате
		 * UTF-16 Big Endian (0xFEFF),
		 * UTF-16 Little Endian (0xFFFE) и
		 * UTF-8 (0xEFBBBF)
		 */
		
		int a, b, c;
		
		if(is.available() >= 2)
		{
			a = is.read();
			b = is.read();
			
			if(a == 0xFE && b == 0xFF)
			{
				return StringEncoder.ENC_UTF16N;
			}
			else if(a == 0xFF && b == 0xFE)
			{
				return StringEncoder.ENC_UTF16L;
			}
			else if(is.available() >= 1)
			{
				c = is.read();
				
				if(a == 0xEF && b == 0xBB && c == 0xBF)
				{
					return StringEncoder.ENC_UTF8;
				}
			}
		}
		
		/* мы считали не сигнатуру, возвращаемся назад */
		is.reset();

		return defenc; // StringEncoder.ENC_DEFAULT;
	}
	
	/**
	 * Считать символ
	 */
	public char readChar() throws IOException
	{
		if(bdis.available() > 0)
		{
			if(enc >= 0)
			{
				return StringEncoder.decodeChar(bdis.read(), enc);
			}
			else if(enc == StringEncoder.ENC_UTF8)
			{
				return bdis.readCharUTF();
			}
			else if(enc == StringEncoder.ENC_UTF16N)
			{
				return bdis.readChar();
			}
			else if(enc == StringEncoder.ENC_UTF16L)
			{
				return bdis.readLeChar();
			}
		}
		
		throw new EOFException();
	}

	/**
	 * Считать символ назад
	 */
	public char readCharBack() throws IOException
	{
		if(bdis.getPosition() > 0)
		{
			if(enc >= 0)
			{
				return StringEncoder.decodeChar(bdis.readBack(), enc);
			}
			else if(enc == StringEncoder.ENC_UTF8)
			{
				return bdis.readCharBackUTF();
			}
			else if(enc == StringEncoder.ENC_UTF16N)
			{
				return bdis.readCharBack();
			}
			else if(enc == StringEncoder.ENC_UTF16L)
			{
				return bdis.readLeCharBack();
			}
		}
		
		throw new EOFException();
	}

	/**
	 * Считать INI-строку.
	 */
	public String readIniLine() throws IOException
	{
		if(available() <= 0)
		{
			// если файл кончился, возвращаем null
			return null;
		}

		StringBuffer buf = new StringBuffer();
		char c;

		String res;
		int index;

		// внешний цикл - читаем строки,
		// пока не встретится не пустая
		while(available() > 0)
		{
			buf.setLength(0);

			// внутренний цикл - чтение строки
			while(available() > 0)
			{
				c = readChar();

				if(c == '\n')
				{
					break;
				}
				else if(c == '\t')
				{
					c = ' ';
				}

				if(c != '\r')
				{
					buf.append(c);
				}
			}

			res = buf.toString();
			index = res.indexOf(';');

			if(index >= 0)
			{
				// убираем комментарии
				res = res.substring(0, index);
			}

			res = res.trim();

			if(res.length() > 0)
			{
				return res;
			}

			res = null;
		}

		return null;
	}
	
	public int gotoLineNum(int linenum) throws IOException
	{
		bdis.setPosition(0);
		detectEncoding(bdis, enc);
		
		int currline = 1;
		
		while(bdis.available() > 0 && currline < linenum)
		{
			if(readChar() == '\n')
			{
				currline++;
			}
		}
		
		return bdis.getPosition();
	}
	
	/**
	 * Поиск текста в потоке.
	 * Если startIndex >= 0, то поиск ведется со startIndex, иначе с текущей позиции.
	 * Если endIndex >= 0, то поиск идет максимум до endIndex, иначе до конца потока.
	 * Если ignoreCase = true, то поиск ведется без учета регистра.
	 * После поиска поток возвращается на прежнюю позицию.
	 */
	public int indexOf(String text, int startIndex, int endIndex, boolean ignoreCase) throws IOException
	{
		int prevpos = bdis.getPosition();
		
		if(startIndex >= 0)
		{
			bdis.setPosition(startIndex);
		}
		
		char[] data;
		
		if(ignoreCase)
		{
			data = Characters.toLowerCase(text).toCharArray();
		}
		else
		{
			data = text.toCharArray();
		}
		
		int end = data.length;
		int index = -1;
		int i = 0;
		
		char c;
		
		while(bdis.available() > 0)
		{
			if(ignoreCase)
			{
				c = Characters.toLowerCase(readChar());
			}
			else
			{
				c = readChar();
			}
			
			if(c == data[i])
			{
				if(index < 0)
				{
					index = bdis.getPosition() - 1;
				}
				
				if(++i >= end)
				{
					break;
				}
			}
			else if(c == data[0])
			{
				index = bdis.getPosition() - 1;
				i = 0;
			}
			else
			{
				index = -1;
				i = 0;
			}
			
			if(endIndex >= 0 && bdis.getPosition() >= endIndex)
			{
				break;
			}
		}
		
		bdis.setPosition(prevpos);
		
		return index;
	}
	
	/**
	 * Сколько еще можно считать из этого потока.
	 * Возвращается количество байт, а не символов,
	 * поэтому при использовании UTF-8 это значение
	 * имеет смысл проверять лишь на неравенство 0.
	 */
	public int available()
	{
		return bdis.available();
	}

	public int getCapacity()
	{
		return bdis.getCapacity() - offset;
	}

	public int getPosition()
	{
		return bdis.getPosition() - offset;
	}

	public void setPosition(int pos) throws IOException
	{
		bdis.setPosition(pos + offset);
	}

	public void skip(int count) throws IOException
	{
		if(enc >= 0)
		{
			bdis.setPosition(bdis.getPosition() + count);
		}
		else
		{
			for(int i = 0; i < count; i++)
			{
				readChar();
			}
		}
	}

	public void rewind(int count) throws IOException
	{
		if(enc >= 0)
		{
			bdis.setPosition(bdis.getPosition() - count);
		}
		else
		{
			for(int i = 0; i < count; i++)
			{
				readCharBack();
			}
		}
	}
	
	/**
	 * Закрытие декодера вместе с потоком, на котором он основан
	 */
	public void close() throws IOException
	{
		bdis.close();
	}
	
//	private static void out(String s)
//	{
//		System.out.println("[InputStreamDecoder] " + s);
//	}
}
