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

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

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

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

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


package java.io;

import malik.emulator.i18n.*;

public class PrintStream extends OutputStream
{
	private static final char[] NULL_STR;

	static
	{
		NULL_STR = new char[] {
				'n', 'u', 'l', 'l'
		};
	}


	private boolean closed;
	private boolean trouble;
	private int count;
	private char[] cbuf;
	private byte[] bbuf;
	private PrintListener[] listeners;
	private OutputStream stream;
	private Object lock;

	public PrintStream(OutputStream stream)
	{
		if(stream == null)
		{
			throw new NullPointerException("PrintStream: " +
					"параметр stream равен нулевой ссылке.");
		}
		this.cbuf = new char[0x0400];
		this.bbuf = new byte[1];
		this.stream = stream;
		this.lock = new Object();
	}

	public void close()
	{
		synchronized(lock)
		{
			if(!closed)
			{
				flushStream();
				closed = true;
				try
				{
					stream.close();
				}
				catch(IOException e)
				{
					invokeSetError();
				}
				stream = null;
			}
		}
	}

	public void flush()
	{
		synchronized(lock)
		{
			flushStream();
		}
	}

	public void write(int src)
	{
		byte[] b;
		OutputStream s;
		synchronized(lock)
		{
			if((s = stream) != null)
			{
				flushStream();
				try
				{
					s.write(src);
				}
				catch(IOException e)
				{
					invokeSetError();
				}
			} else
			{
				invokeSetError();
			}
		}
		synchronized(cbuf)
		{
			(b = bbuf)[0] = (byte) src;
			notifyListenersWrite(b, 0, 1);
		}
	}

	public void write(byte[] src)
			throws IOException
	{
		int length;
		OutputStream s;
		if(src == null)
		{
			throw new NullPointerException("PrintStream.write: " +
					"параметр src равен нулевой ссылке.");
		}
		synchronized(lock)
		{
			if((s = stream) != null)
			{
				flushStream();
				try
				{
					s.write(src);
				}
				catch(IOException e)
				{
					invokeSetError();
				}
			} else
			{
				invokeSetError();
			}
		}
		if((length = src.length) <= 0)
		{
			return;
		}
		notifyListenersWrite(src, 0, length);
	}

	public void write(byte[] src, int offset, int length)
	{
		int lim;
		int len;
		OutputStream s;
		if(src == null)
		{
			throw new NullPointerException("PrintStream.write: " +
					"параметр src равен нулевой ссылке.");
		}
		if((lim = offset + length) > (len = src.length) ||
				lim < offset || offset > len || offset < 0)
		{
			throw new ArrayIndexOutOfBoundsException("PrintStream.write: " +
					"индекс выходит из диапазона.");
		}
		synchronized(lock)
		{
			if((s = stream) != null)
			{
				flushStream();
				try
				{
					s.write(src, offset, length);
				}
				catch(IOException e)
				{
					invokeSetError();
				}
			} else
			{
				invokeSetError();
			}
		}
		if(length <= 0)
		{
			return;
		}
		notifyListenersWrite(src, offset, length);
	}

	public void print(boolean src)
	{
		synchronized(lock)
		{
			write(src ? "true" : "false", false);
		}
	}

	public void print(char src)
	{
		synchronized(lock)
		{
			write(src, false);
		}
	}

	public void print(float src)
	{
		synchronized(lock)
		{
			write(Float.toString(src), false);
		}
	}

	public void print(double src)
	{
		synchronized(lock)
		{
			write(Double.toString(src), false);
		}
	}

	public void print(int src)
	{
		synchronized(lock)
		{
			write(Integer.toString(src), false);
		}
	}

	public void print(long src)
	{
		synchronized(lock)
		{
			write(Long.toString(src), false);
		}
	}

	public void print(char[] src)
	{
		synchronized(lock)
		{
			write(src, false);
		}
	}

	public void print(String src)
	{
		synchronized(lock)
		{
			write(src, false);
		}
	}

	public void print(Object src)
	{
		synchronized(lock)
		{
			write(String.valueOf(src), false);
		}
	}

	public void println()
	{
		synchronized(lock)
		{
			writeln();
		}
	}

	public void println(boolean src)
	{
		synchronized(lock)
		{
			write(src ? "true" : "false", true);
		}
	}

	public void println(char src)
	{
		synchronized(lock)
		{
			write(src, true);
		}
	}

	public void println(float src)
	{
		synchronized(lock)
		{
			write(Float.toString(src), true);
		}
	}

	public void println(double src)
	{
		synchronized(lock)
		{
			write(Double.toString(src), true);
		}
	}

	public void println(int src)
	{
		synchronized(lock)
		{
			write(Integer.toString(src), true);
		}
	}

	public void println(long src)
	{
		synchronized(lock)
		{
			write(Long.toString(src), true);
		}
	}

	public void println(char[] src)
	{
		synchronized(lock)
		{
			write(src, true);
		}
	}

	public void println(String src)
	{
		synchronized(lock)
		{
			write(src, true);
		}
	}

	public void println(Object src)
	{
		synchronized(lock)
		{
			write(String.valueOf(src), true);
		}
	}

	public boolean checkError()
	{
		boolean result;
		synchronized(lock)
		{
			if(stream != null)
			{
				flushStream();
			}
			result = trouble;
		}
		return result;
	}

	public final void addPrintListener(PrintListener listener)
	{
		int i;
		PrintListener[] listeners;
		if(listener == null)
		{
			return;
		}
		synchronized(cbuf)
		{
			if((listeners = this.listeners) == null)
			{
				listeners = this.listeners = new PrintListener[2];
			}
			if(indexOfListener(listener) < 0)
			{
				if((i = indexOfListener(null)) < 0)
				{
					Array.copy(listeners, 0, listeners = this.listeners =
							new PrintListener[(i = listeners.length) << 1], 0, i);
				}
				listeners[i] = listener;
			}
		}
	}

	public final void removePrintListener(PrintListener listener)
	{
		int i;
		PrintListener[] listeners;
		if(listener == null)
		{
			return;
		}
		synchronized(cbuf)
		{
			if((listeners = this.listeners) != null && (i = indexOfListener(listener)) >= 0)
			{
				listeners[i] = null;
			}
		}
	}

	public final Object getMonitor()
	{
		return lock;
	}

	protected void setError()
	{
	}

	protected final void invokeSetError()
	{
		trouble = true;
		try
		{
			setError();
		}
		catch(RuntimeException e)
		{
			/*
			 * Проигнорировать исключение.
			 * Выражение вида
			 * e.printStackTrace();
			 * может вызвать переполнение стека вызовов.
			 */
		}
	}

	private void notifyListenersWrite(byte[] src, int offset, int length)
	{
		int i;
		PrintListener[] listeners;
		PrintListener listener;
		synchronized(cbuf)
		{
			for(i = (listeners = this.listeners) != null ? listeners.length : 0; i-- > 0; )
			{
				if((listener = listeners[i]) == null)
				{
					continue;
				}
				listener.write(src, offset, length);
			}
		}
	}

	private void notifyListenersPrint(char src, boolean newLine)
	{
		int i;
		PrintListener[] listeners;
		PrintListener listener;
		synchronized(cbuf)
		{
			for(i = (listeners = this.listeners) != null ? listeners.length : 0; i-- > 0; )
			{
				if((listener = listeners[i]) == null)
				{
					continue;
				}
				listener.print(src);
				if(!newLine)
				{
					continue;
				}
				listener.println();
			}
		}
	}

	private void notifyListenersPrint(char[] src, boolean newLine)
	{
		int i;
		PrintListener[] listeners;
		PrintListener listener;
		synchronized(cbuf)
		{
			for(i = (listeners = this.listeners) != null ? listeners.length : 0; i-- > 0; )
			{
				if((listener = listeners[i]) == null)
				{
					continue;
				}
				listener.print(src);
				if(!newLine)
				{
					continue;
				}
				listener.println();
			}
		}
	}

	private void notifyListenersPrint(String src, boolean newLine)
	{
		int i;
		PrintListener[] listeners;
		PrintListener listener;
		synchronized(cbuf)
		{
			for(i = (listeners = this.listeners) != null ? listeners.length : 0; i-- > 0; )
			{
				if((listener = listeners[i]) == null)
				{
					continue;
				}
				listener.print(src);
				if(!newLine)
				{
					continue;
				}
				listener.println();
			}
		}
	}

	private void notifyListenersPrintln()
	{
		int i;
		PrintListener[] listeners;
		PrintListener listener;
		synchronized(cbuf)
		{
			for(i = (listeners = this.listeners) != null ? listeners.length : 0; i-- > 0; )
			{
				if((listener = listeners[i]) == null)
				{
					continue;
				}
				listener.println();
			}
		}
	}

	private void write(char src, boolean newLine)
	{
		int c;
		char[] b;
		if(stream != null)
		{
			if((c = count) > (b = cbuf).length - 3)
			{
				flushStream();
				c = 0;
			}
			b[c++] = src;
			if(newLine)
			{
				b[c++] = 13;
				b[c++] = 10;
			}
			count = c;
		}
		notifyListenersPrint(src, newLine);
	}

	private void write(char[] src, boolean newLine)
	{
		int c;
		int d;
		int i;
		int r;
		int sl;
		int bl;
		char[] b;
		if(src == null)
		{
			Array.copy(NULL_STR, 0, src = new char[c = NULL_STR.length], 0, c);
		}
		if(stream != null)
		{
			if((c = count) > (bl = (b = cbuf).length) - (sl = src.length) - 2)
			{
				flushStream();
				c = 0;
			}
			for(i = 0; (r = sl - i) > 0; )
			{
				Array.copy(src, i, b, c, d = Math.min(bl - c, r));
				count = (c += d);
				if((i += d) < sl)
				{
					flushStream();
					c = 0;
				}
			}
			if(newLine)
			{
				if(c > bl - 2)
				{
					flushStream();
					c = 0;
				}
				b[c++] = 13;
				b[c++] = 10;
				count = c;
			}
		}
		notifyListenersPrint(src, newLine);
	}

	private void write(String src, boolean newLine)
	{
		int c;
		int d;
		int i;
		int r;
		int sl;
		int bl;
		char[] b;
		if(src == null)
		{
			src = String.valueOf(src);
		}
		if(stream != null)
		{
			if((c = count) > (bl = (b = cbuf).length) - (sl = src.length()) - 2)
			{
				flushStream();
				c = 0;
			}
			for(i = 0; (r = sl - i) > 0; )
			{
				src.getChars(i, i + (d = Math.min(bl - c, r)), b, c);
				count = (c += d);
				if((i += d) < sl)
				{
					flushStream();
					c = 0;
				}
			}
			if(newLine)
			{
				if(c > bl - 2)
				{
					flushStream();
					c = 0;
				}
				b[c++] = 13;
				b[c++] = 10;
				count = c;
			}
		}
		notifyListenersPrint(src, newLine);
	}

	private void writeln()
	{
		int c;
		char[] b;
		if(stream != null)
		{
			if((c = count) > (b = cbuf).length - 2)
			{
				flushStream();
				c = 0;
			}
			b[c++] = 13;
			b[c++] = 10;
			count = c;
		}
		notifyListenersPrintln();
	}

	private void flushStream()
	{
		int c;
		OutputStream s;
		if((s = stream) != null)
		{
			try
			{
				if((c = count) > 0)
				{
					count = 0;
					s.write(Helper.charToByteArray(cbuf, 0, c));
					s.flush();
				}
			}
			catch(IOException e)
			{
				invokeSetError();
			}
		} else
		{
			invokeSetError();
		}
	}

	private int indexOfListener(PrintListener listener)
	{
		int i;
		PrintListener[] listeners;
		for(i = (listeners = this.listeners).length; i-- > 0; )
		{
			if(listeners[i] == listener)
			{
				return i;
			}
		}
		return -1;
	}
}
