package com.vmx;

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

/**
 * Вспомогательный класс.
 *
 * Сюда пихается все, что должно быть доступно вне пакета с ФМ,
 * но на самостоятельную библиотеку не тянет.
 */
public class AuxClass
{
	public static final int COPYBUFSIZE = 65536;
	public static final int ARCBUFSIZE = 8192;

	public static final long MIN_THREAD_SLEEP = 10;

	public static final String RESTRICTED_CHARS = "\\/:*?\"<>|";
	public static final String SEPARATOR_CHARS = " *+-/;=|";

	public static final String YEAR			= "[Y]";
	public static final String YEAR_SHORT	= "[y]";
	public static final String MONTH		= "[M]";
	public static final String DAY_OF_MONTH	= "[D]";

	public static final String HOUR_OF_DAY	= "[h]";
	public static final String MINUTE		= "[m]";
	public static final String SECOND		= "[s]";

	public static Random rnd = new Random();
	public static boolean duplexFileAccess = true;
	public static String timeToFileNameFormat = "[y][M][D]_[h][m][s]";

	// Названия хранилищ RMS
    public static final String[] STORE_NAMES =
    {
        "options",		// 0
        "favorites",	// 1
        "bookmarks",	// 2
        "panels",		// 3
        "imgcache",		// 4
		"mdlicons",		// 5
		"mdldata",		// 6
		"filetypes",	// 7
		"uiicons",		// 8
		"templates",	// 9
		"fontpaths",	// 10
		"mimetypes"		// 11
    };

	public static final String STORE_PREFIX = "UniFM_"; // +
	//Integer.toHexString((new AuxClass()).getClass().getName().hashCode()) + "_";

	/**
	 * Получить название хранилища RMS в том виде,
	 * в каком его следует использовать в программе.
	 *
	 * @param index номер хранилища
	 * @return имя хранилища
	 */
    public static String getStoreName(int index)
    {
        return STORE_PREFIX + STORE_NAMES[index];
    }

	/**
	 * Достать случайное число в диапазоне от from до to включительно.
	 */
	public static int randomFromRange(int from, int to)
	{
		return from + (rnd.nextInt() & 0x7FFFFFFF) % (to - from + 1);
	}

	/**
	 * Достать случайное число в диапазоне от from до to включительно,
	 * причем такое, чтобы оно отличалось от prev.
	 */
	public static int randomFromRange(int from, int to, int prev)
	{
		if(to - from < 1)
		{
			return randomFromRange(from, to);
		}

		int curr;

		do
		{
			curr = randomFromRange(from, to);
		}
		while(curr == prev);

		return curr;
	}

	/**
	 * Сформировать последовательность неповторяющихся случайных чисел
	 * в диапазоне от from до to длиной count.
	 */
	public static int[] randomSequence(int from, int to, int count)
	{
		boolean[] flags = new boolean[to - from + 1];

		if(count <= 0 || count > flags.length)
		{
			count = flags.length;
		}

		int[] res = new int[count];

		for(int i = 0; i < flags.length; i++)
		{
			flags[i] = false;
		}

		int index;

		for(int i = 0; i < count; i++)
		{
			index = (rnd.nextInt() & 0x7FFFFFFF) % flags.length;

			if((index & 1) == 0)
			{
				while(flags[index])
				{
					if(++index >= flags.length)
					{
						index = 0;
					}
				}
			}
			else
			{
				while(flags[index])
				{
					if(--index < 0)
					{
						index = flags.length - 1;
					}
				}
			}

			res[i] = from + index;
			flags[index] = true;
		}

		return res;
	}

	/**
	 * Проверить имя файла на наличие недопустимых символов.
	 * К недопустимым символам относятся \ / : * ? " < > |
	 *
	 * @param format проверяемое имя файла
	 * @return true, если имя файла не содержит недопустимых символов
	 */
	public static boolean checkFileName(String filename)
	{
		char[] cs = RESTRICTED_CHARS.toCharArray();

		for(int i = 0; i < cs.length; i++)
		{
			if(filename.indexOf(cs[i]) >= 0)
			{
				return false;
			}
		}

		return true;
	}

	/**
	 * Заменить в имени файла все недопустимые символы
	 * на подчеркивания _
	 *
	 * @param format имя файла
	 * @return имя файла, не содержащее недопустимых символов
	 */
	public static String validateFileName(String filename)
	{
		char[] cs = RESTRICTED_CHARS.toCharArray();

		for(int i = 0; i < cs.length; i++)
		{
			filename.replace(cs[i], '_');
		}

		return filename;
	}

	/**
	 * Вернуть общую часть имен файлов fileA и fileB.
	 *
	 * Например, для двух файлов:
	 *
	 * c:/folder1/folder2/folder10/folder20/file1.txt
	 * c:/folder3/folder10/folder20/file1.txt
	 *
	 * общей частью будет являться строка folder10/folder20/file1.txt
	 *
	 * @param fileA имя первого файла
	 * @param fileB имя второго файла
	 * @return общая часть имен файлов, или имя первого файла
	 */
	public static String getSharedNamePart(String fileA, String fileB)
	{
		if(fileA.equals(fileB))
		{
			return fileA;
		}

		int indexA = fileA.lastIndexOf('/', fileA.endsWith("/") ? fileA.length() - 2 : fileA.length() - 1);
		int indexB = fileB.lastIndexOf('/', fileB.endsWith("/") ? fileB.length() - 2 : fileB.length() - 1);

		int index = -1;

		while(fileA.substring(indexA + 1).equals(fileB.substring(indexB + 1)))
		{
			index = indexA;

			indexA = fileA.lastIndexOf('/', indexA - 1);
			indexB = fileB.lastIndexOf('/', indexB - 1);
		}
		
		return fileA.substring(index + 1);
	}

	/**
	 * Сделать из потенциально любой строки расширение файла.
	 *
	 * Если строка есть null, возвращается пустая строка.
	 * Иначе к ней, если нужно, приклеивается спереди точка.
	 *
	 * @param ext ...
	 * @return расширение файла
	 */
	public static String formatFileExtension(String ext)
	{
		if(ext == null)
		{
			return "";
		}

		if(ext.charAt(0) != '.')
		{
			ext = "." + ext;
		}

		return ext;
	}

	/**
	 * Достать расширение из имени файла.
	 *
	 * @param format имя файла
	 * @return расширение, если есть, или исходное имя, если нету
	 */
	public static String getFileExtension(String filename)
	{
		int index = filename.lastIndexOf('/');

		if(index >= 0)
		{
			filename = filename.substring(index + 1);
		}

		index = filename.lastIndexOf('.');

		if(index >= 0)
		{
			return filename.substring(index + 1);
		}

		return filename;
	}

	/**
	 * Превратить строчку в число.
	 * Если это не получилось, возвращается второй параметр.
	 *
	 * @param s строчка с числом
	 * @param v значение, которое вернется, если преобразование не удастся
	 * @return число, содержащееся в строчке, или второй параметр
	 */
	public static int parseInt(String s, int v)
	{
		try
		{
			return Integer.parseInt(s);
		}
		catch(Exception e)
		{
			return v;
		}
	}

	/**
	 * Превратить hex-строчку в число.
	 * Если это не получилось, возвращается второй параметр.
	 *
	 * @param s строчка с числом в hex
	 * @param v значение, которое вернется, если преобразование не удастся
	 * @return число, содержащееся в строчке, или второй параметр
	 */
	public static int parseHexInt(String s, int v)
	{
		try
		{
			return (int)Long.parseLong(s, 16);
		}
		catch(Exception e)
		{
			return v;
		}
	}

	/**
	 * Превратить строчку в дробное число.
	 * Если это не получилось, возвращается второй параметр.
	 *
	 * @param s строчка с дробным числом
	 * @param v значение, которое вернется, если преобразование не удастся
	 * @return число, содержащееся в строчке, или второй параметр
	 */
	public static float parseFloat(String s, float v)
	{
		try
		{
			return Float.parseFloat(s);
		}
		catch(Exception e)
		{
			return v;
		}
	}

	/**
	 * Преобразовать double d в строку с точностью afterdot знаков после '.'
	 */
	public static String doubleToString(double d, int afterdot)
	{
		int factor = 1;

		for(int i = 0; i < afterdot; i++)
		{
			factor *= 10;
		}

		if(afterdot == 0)
		{
			return Integer.toString((int)Math.floor(d * factor + 0.5) / factor);
		}
		else
		{
			return Double.toString(Math.floor(d * factor + 0.5) / factor);
		}
	}

	/**
	 * Преобразовать value в строку, дополнив спереди нулями до pad знаков.
	 */
	public static String integerToString(int value, int pad)
	{
		StringBuffer res = new StringBuffer(pad + 1);

		res.append(Math.abs(value));

		while(res.length() < pad)
		{
			res.insert(0, '0');
		}

		if(value < 0)
		{
			res.insert(0, '-');
		}

		return res.toString();
	}

	/**
	 * Преобразовать массив чисел в строку с этими числами в hex виде.
	 *
	 * @param arr массив чисел
	 * @param spacer чем разделять числа, например " " или "-"
	 * @return строка вида "12345678-9ABCDEFF"
	 */
	public static String intArrayToHexString(int[] arr, String spacer)
	{
		if(arr.length == 0)
		{
			return "";
		}

		String temp = Integer.toHexString(arr[0]).toUpperCase();

		for(int i = 1; i < arr.length; i++)
		{
			temp += spacer + Integer.toHexString(arr[i]).toUpperCase();
		}

		return temp;
	}

	/**
	 * Преобрразовать число с фиксированной точкой в строку.
	 *
	 * @param value число
	 * @param shift позиция фиксированной точки
	 * @return строка с числом
	 */
	public static String formatFPNumber(int value, int shift)
	{
		return Double.toString((double)value / (double)(1 << shift));
	}

	/**
	 * Преобразовать Exception в строку.
	 */
	public static String formatException(Throwable x)
	{
		String msg = x.getMessage();

		if(msg != null)
		{
			msg = msg.trim();

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

		return x.getClass().getName();
	}

	/**
	 * Поместить элементы из Enumeration в вектор.
	 * Если вместо вектора передать null,
	 * будет создан новый вектор.
	 *
	 * @param source Enumeration, из которого брать элементы
	 * @param target вектор, в который их класть, или null
	 * @return тот же вектор
	 */
	public static Vector enumerationToVector(Enumeration source, Vector target)
	{
		if(target == null)
		{
			target = new Vector();
		}

		while(source.hasMoreElements())
		{
			target.addElement(source.nextElement());
		}

		return target;
	}

	/**
	 * Проверить, существует ли класс с указанным именем.
	 *
	 * @param name имя класса
	 * @return true, если такой класс существует
	 */
	public static boolean classExists(String name)
	{
//		return true;

		try
		{
			return Class.forName(name) != null;
		}
		catch(Throwable t)
		{
			return false;
		}
	}

	/**
	 * Копировать данные из одного потока в другой.
	 *
	 * @param is исходный поток
	 * @param os конечный поток
	 * @param callback для наблюдения за прогрессом
	 * @return сколько байт скопировано
	 */
	public static int copyStream(InputStream is, OutputStream os, ProgressCallback callback) throws IOException
	{
		byte[] buf = new byte[COPYBUFSIZE];
		int len, total;

		total = 0;
		callback.setMax(is.available());

		boolean flag = true;

		while((len = is.read(buf)) > 0)
		{
			os.write(buf, 0, len);
			total += len;

			if(flag && callback.getMax() < total)
			{
				callback.setMax(-1);
				flag = false;
			}

			callback.setProgress(total);
		}

		return total;
	}

	/**
	 * Записать блок данных в файл.
	 * Запись производится в отрезок со start до end,
	 * при этом размер блока данных может не соответствовать
	 * размеру отрезка (в этом случае изменяется размер файла).
	 */
	private static int updateFileData(FileConnection fc, byte[] data, int start, int end) throws IOException
	{
		int oldlen = end - start;
		int newlen = data.length;

		if(oldlen < 0 || newlen < 0)
		{
			return 0;
		}

		OutputStream os;

		RandomAccessInputStream is = new FileInputStream(fc);

		int oldsize = is.getCapacity();

		/* переходим к началу данных,
		   находящихся ПОСЛЕ изменяемых */
		is.setPosition(end);

		if(newlen < oldlen) // если так, то удаляем кусок потока
		{
			// то есть копируем данные после изменяемых со смещением НАЗАД

			byte[] copybs = new byte[COPYBUFSIZE];

			os = fc.openOutputStream(start + newlen);

			while(is.available() > 0)
			{
				os.write(copybs, 0, is.read(copybs));
			}

			os.close();
			is.close();

			fc.truncate(oldsize + newlen - oldlen);
		}
		else if(newlen > oldlen) // а если так, то вставляем свободного места
		{
			// то есть копируем данные после изменяемых со смещением ВПЕРЕД

			byte[] copybs = new byte[COPYBUFSIZE];

			int copylen = is.available();
			int copycnt = (copylen + COPYBUFSIZE - 1) / COPYBUFSIZE;
			int i = copycnt - 1, cblen;

			if(copycnt > 0)
			{
				/*
				 * Здесь проверка:
				 * Сколько еще нужно дописать в файл, чтобы можно было
				 * перейти в нем к началу БУДУЩЕГО ПОСЛЕДНЕГО скопированного блока
				 * (чтобы можно было уже ТАМ открыть OutputStream и писать этот блок).
				 * Этого достаточно, поскольку блоки копируются от последнего к первому.
				 */
				int len = (start + newlen + i * COPYBUFSIZE) - oldsize + 1;

				if(len > 0)
				{
					os = fc.openOutputStream(oldsize);

					while(len > 0)
					{
						cblen = Math.min(len, COPYBUFSIZE);
						os.write(copybs, 0, cblen);
						len -= cblen;
					}

					os.close();
				}

				do
				{
					is.setPosition(end + i * COPYBUFSIZE);
					cblen = is.read(copybs);

					// здесь проверка, чтобы не прихватить
					// вместе с данными пустышку, которую дописали выше
					if(is.getPosition() > oldsize)
					{
						cblen -= is.getPosition() - oldsize;
					}

					os = fc.openOutputStream(start + newlen + i * COPYBUFSIZE);
					os.write(copybs, 0, cblen);
					os.close();
				}
				while(--i >= 0);
			}

			is.close();
		}
		else
		{
			is.close();
		}

		// записываем данные
		os = fc.openOutputStream(start);
		os.write(data);
		os.close();

		return newlen - oldlen;
	}

	/**
	 * Записать данные замены из RISов в файл.
	 * При этом сначала проверить, как это получится быстрее:
	 * последовательно заменять в файле по одному блоку,
	 * или сразу переписать весь файл из последнего RISа.
	 *
	 * Если потоки замены основаны на временных файлах,
	 * эти временные файлы автоматически удаляются.
	 */
	public static FileConnection updateFileData(FileConnection fc, ReplaceableInputStream ris, ProgressCallback callback) throws IOException
	{
		Vector vector = new Vector();
		ris.traverseReplaceChain(vector);

		FileConnection temp;

		if(duplexFileAccess && ris.getWriteCount() < ris.getCapacity())
		{
			ris.getBaseStream().close();

			if(callback != null)
			{
				callback.setProgress(0);
				callback.setMax(vector.size());
			}

			fc = getWriteableFileConnection(fc);

			for(int i = 0; i < vector.size(); i++)
			{
				ris = (ReplaceableInputStream)vector.elementAt(i);
				updateFileData(fc, ris.getReplace(), ris.getReplaceStart(), ris.getReplaceEnd());

				if(callback != null)
				{
					callback.progress(1);
				}
			}
		}
		else
		{
			String tempname = Connector.createTempFile(fc.getPath(), true);

			temp = (FileConnection)Connector.open("file:///" + tempname);
			temp.create();

			OutputStream os = temp.openOutputStream();
			ris.setPosition(0);

			if(callback != null)
			{
				callback.setProgress(0);
				callback.setMax(ris.getCapacity());
			}

			byte[] buf = new byte[COPYBUFSIZE];

			if(callback != null)
			{
				int len;

				while(ris.available() > 0)
				{
					os.write(buf, 0, len = ris.read(buf));
					callback.progress(len);
				}
			}
			else
			{
				while(ris.available() > 0)
				{
					os.write(buf, 0, ris.read(buf));
				}
			}

			os.close();
			ris.getBaseStream().close();

			fc = getWriteableFileConnection(fc);

			String name = fc.getName();
			fc.delete();
			fc.close();

			temp.rename(name);

			Connector.releaseTempFile(tempname);

			fc = temp;
		}

		RandomAccessInputStream replace;
		
		for(int i = 0; i < vector.size(); i++)
		{
			ris = (ReplaceableInputStream)vector.elementAt(i);
			replace = ris.getReplaceStream();
			ris.close();

			if(replace instanceof FileInputStream)
			{
				temp = ((FileInputStream)replace).getBaseConnection();
				replace.close();

				Connector.releaseTempFile(temp.getPath() + temp.getName());

				temp.delete();
				temp.close();
			}
			else
			{
				replace.close();
			}
		}

		return fc;
	}

	public static FileConnection getWriteableFileConnection(FileConnection file) throws IOException
	{
		try
		{
			file.truncate(file.fileSize());
			return file;
		}
		catch(Exception e)
		{
			file.close();
			return (FileConnection)Connector.open(file.getURL(), Connector.READ_WRITE);
		}
	}

	/**
	 * Преобразовать время в строку заданного формата
	 */
	public static String formatTime(String format, long time)
	{
		Calendar cal = Calendar.getInstance();

		if(time >= 0)
		{
			cal.setTime(new Date(time));
		}

		String year = AuxClass.integerToString(cal.get(Calendar.YEAR), 2);
		year = year.substring(year.length() - 2);

		format = TextProcessor.replaceText(format, YEAR, AuxClass.integerToString(cal.get(Calendar.YEAR), 4));
		format = TextProcessor.replaceText(format, YEAR_SHORT, year);
		format = TextProcessor.replaceText(format, MONTH, AuxClass.integerToString(cal.get(Calendar.MONTH) + 1, 2));
		format = TextProcessor.replaceText(format, DAY_OF_MONTH, AuxClass.integerToString(cal.get(Calendar.DAY_OF_MONTH), 2));

		format = TextProcessor.replaceText(format, HOUR_OF_DAY, AuxClass.integerToString(cal.get(Calendar.HOUR_OF_DAY), 2));
		format = TextProcessor.replaceText(format, MINUTE, AuxClass.integerToString(cal.get(Calendar.MINUTE), 2));
		format = TextProcessor.replaceText(format, SECOND, AuxClass.integerToString(cal.get(Calendar.SECOND), 2));

		return format;
	}

	public static String timeToFileName(long time)
	{
		return formatTime(timeToFileNameFormat, time);
	}
	
	/**
	 * Перевод времени в строку вида 050101_150000
	 */
//	public static String timeToFileName(long time)
//	{
//		Calendar cal = Calendar.getInstance();
//
//		if(time >= 0)
//		{
//			cal.setTime(new Date(time));
//		}
//
//		StringBuffer res = new StringBuffer(13);
//		int temp;
//
//		res.append(cal.get(Calendar.YEAR));
//
//		while(res.length() > 2)
//		{
//			res.deleteCharAt(0);
//		}
//
//		temp = cal.get(Calendar.MONTH) + 1;
//
//		if(temp < 10)
//		{
//			res.append('0');
//		}
//
//		res.append(temp);
//
//		temp = cal.get(Calendar.DAY_OF_MONTH);
//
//		if(temp < 10)
//		{
//			res.append('0');
//		}
//
//		res.append(temp);
//		res.append('_');
//
//		temp = cal.get(Calendar.HOUR_OF_DAY);
//
//		if(temp < 10)
//		{
//			res.append('0');
//		}
//
//		res.append(temp);
//
//		temp = cal.get(Calendar.MINUTE);
//
//		if(temp < 10)
//		{
//			res.append('0');
//		}
//
//		res.append(temp);
//
//		temp = cal.get(Calendar.SECOND);
//
//		if(temp < 10)
//		{
//			res.append('0');
//		}
//
//		res.append(temp);
//
//		return res.toString();
//	}

	/**
	 * Перевод времени в строку вида 01.01.2005 15:00:00
	 */
	public static String timeToString(long time, boolean millis)
	{
		Calendar cal = Calendar.getInstance();

		if(time >= 0)
		{
			cal.setTime(new Date(time));
		}

		StringBuffer res = new StringBuffer(19);
		int temp;
		
		temp = cal.get(Calendar.DAY_OF_MONTH);

		if(temp < 10)
		{
			res.append('0');
		}

		res.append(temp);
		res.append('.');
		
		temp = cal.get(Calendar.MONTH) + 1;

		if(temp < 10)
		{
			res.append('0');
		}

		res.append(temp);
		res.append('.');

		res.append(cal.get(Calendar.YEAR));
		res.append(' ');

		temp = cal.get(Calendar.HOUR_OF_DAY);

		if(temp < 10)
		{
			res.append('0');
		}

		res.append(temp);
		res.append(':');

		temp = cal.get(Calendar.MINUTE);

		if(temp < 10)
		{
			res.append('0');
		}

		res.append(temp);
		res.append(':');

		temp = cal.get(Calendar.SECOND);

		if(temp < 10)
		{
			res.append('0');
		}

		res.append(temp);

		if(millis)
		{
			res.append('.');

			temp = cal.get(Calendar.MILLISECOND);

			if(temp < 10)
			{
				res.append('0');
				res.append('0');
			}
			else if(temp < 100)
			{
				res.append('0');
			}

			res.append(temp);
		}

		return res.toString();
	}

	/**
	 * Перевод времени из МИКРОсекунд в строку вида 0:00
	 */
	public static String mediaTimeToString(long time)
	{
		if(time < 0)
		{
			return "";
		}

		StringBuffer res = new StringBuffer();

		int seconds = (int)(time / 1000000);

		int minutes = seconds / 60;
		seconds %= 60;

		int hours = minutes / 60;
		minutes %= 60;

		if(hours > 0)
		{
			res.append(hours);
			res.append(':');

			if(minutes < 10)
			{
				res.append('0');
			}
		}

		res.append(minutes);
		res.append(':');

		if(seconds < 10)
		{
			res.append('0');
		}

		res.append(seconds);

		return res.toString();
	}

	/**
	 * Получить строку с текущим временем
	 */
	public static String getCurrentTime()
	{
		Calendar cal = Calendar.getInstance();

		StringBuffer res = new StringBuffer();
		int time;

		time = cal.get(Calendar.HOUR_OF_DAY);

		if(time < 10)
		{
			res.append('0');
		}

		res.append(time);
		res.append(':');

		time = cal.get(Calendar.MINUTE);

		if(time < 10)
		{
			res.append('0');
		}

		res.append(time);

		return res.toString();
	}

	/**
	 * Преобразовать число в строку вида 12,345,678
	 */
	public static String formatNumber(long value)
	{
		if(value < 0)
		{
			return "-" + formatNumber(-value);
		}

		StringBuffer res = new StringBuffer();
		String str;

		while(true)
		{
			if(value > 999)
			{
				res.insert(0, str = Long.toString(value % 1000));

				if(str.length() == 1)
				{
					res.insert(0, ",00");
				}
				else if(str.length() == 2)
				{
					res.insert(0, ",0");
				}
				else
				{
					res.insert(0, ",");
				}

				value /= 1000;
			}
			else
			{
				res.insert(0, Long.toString(value));
				break;
			}
		}

		return res.toString();
	}

	public static InputStream getResourceAsStream(String name)
	{
		if(!name.startsWith("/"))
		{
			name = "/" + name;
		}

		return Object.class.getResourceAsStream(name);
	}

	public static int allocateMemory(int size, int chunk)
	{
		Vector arrays = new Vector();
		int allocated = 0;

		if(chunk <= 0)
		{
			chunk = 65536;
		}

		try
		{
			while(true)
			{
				byte[] b = new byte[chunk];
				arrays.addElement(b);

				allocated += chunk;

				if(size > 0 && allocated >= size)
				{
					break;
				}
			}
		}
		catch(OutOfMemoryError oome)
		{
		}

		return allocated;
	}

	public static int readLeInt(InputStream is) throws IOException
	{
		return (is.read() & 0xFF) | ((is.read() & 0xFF) << 8) | ((is.read() & 0xFF) << 16) | ((is.read() & 0xFF) << 24);
	}

	public static int readLeShort(InputStream is) throws IOException
	{
		return (is.read() & 0xFF) | ((is.read() & 0xFF) << 8);
	}

	public static void writeLeInt(OutputStream os, int value) throws IOException
	{
		os.write(value & 0xFF);
		os.write((value >> 8) & 0xFF);
		os.write((value >> 16) & 0xFF);
		os.write((value >> 24) & 0xFF);
	}

	public static void writeLeShort(OutputStream os, int value) throws IOException
	{
		os.write(value & 0xFF);
		os.write((value >> 8) & 0xFF);
	}

	/**
	 * Достать логарифм числа по основанию 2.
	 * По сути функция ищет первый ненулевой бит.
	 */
	public static int logBaseTwo(int value)
	{
		if(value == 0)
		{
			return 0;
		}
		else
		{
			value = Math.abs(value);
		}

		int power = 31;
		
		while((value & (1 << power)) == 0)
		{
			power--;
		}

		return power;
	}

	/**
	 * Округлить число до ближайшей степени двойки.
	 */
	public static int roundBaseTwo(int value)
	{
		value &= 0x3FFFFFFF;

		if(value == 0)
		{
			return 0x40000000;
		}

		// 00001011 01110011 00010001 10100111
		//     ^
		// 00001000 00000000 00000000 00000000
		// 00010000 00000000 00000000 00000000
		// 00011000 00000000 00000000 00000000
		// 00001100 00000000 00000000 00000000

		int power = logBaseTwo(value);

		int low = 1 << power;
		int high = low << 1;

		int mid = (low + high) >>> 1;

		if(value >= mid)
		{
			return high;
		}
		else
		{
			return low;
		}
	}

	/**
	 * Вывести очень хитрое отладочное сообщение.
	 */
	public static void out(Object object, String text)
	{
		try
		{
			throw new RuntimeException("[" + Integer.toHexString(object.hashCode()).toUpperCase() + "] " + text);
		}
		catch(RuntimeException ex)
		{
			System.out.println(ex.getMessage());
//			ex.printStackTrace();
		}
	}
}
