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

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

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

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

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


package javax.microedition.lcdui;

import java.lang.ref.*;
import java.util.*;
import malik.emulator.media.graphics.*;
import malik.emulator.time.*;

public class DateField extends Item
{
	private static final class TimeZonesListCommandHandler extends WeakReference
			implements CommandListener
	{
		TimeZonesListCommandHandler(DateField thisItem)
		{
			super(thisItem);
		}

		public void commandAction(Command command, Displayable screen)
		{
			int i;
			int f;
			List list;
			DateField thisItem;
			if((thisItem = (DateField) get()) == null || screen != (list = thisItem.timeZonesList))
			{
				return;
			}
			if(command == List.SELECT_COMMAND)
			{
				i = thisItem.offset;
				f = thisItem.offset = list.getSelectedIndex();
				thisItem.showSourceScreen();
				if(i != f)
				{
					thisItem.invokeNotifyStateChanged();
				}
				return;
			}
			if(command == BACK)
			{
				thisItem.showSourceScreen();
			}
		}
	}

	private static abstract class Component extends WeakReference
	{
		protected static final long DAY_MASK = 0xffL << 32;


		public boolean visible;
		public int left;
		public int top;
		public int width;
		public int height;

		protected Component(DateField thisItem)
		{
			super(thisItem);
		}

		public abstract void paint(Graphics render, int width, int height,
				boolean focused, boolean pressed, long fields, String offset);

		public abstract int getWidth();

		public abstract int getHeight();

		public void keyPressed(int keyCode)
		{
		}

		public void pointerPressed(int x, int y)
		{
		}

		public void pointerDragged(int x, int y)
		{
		}

		public void pointerReleased(int x, int y)
		{
		}

		public void select()
		{
		}

		public boolean hasSelectCommand()
		{
			return false;
		}

		public boolean traverse(int direction, boolean focused)
		{
			return false;
		}

		protected final void repaint()
		{
			Item thisItem;
			if((thisItem = (Item) get()) == null)
			{
				return;
			}
			thisItem.notifyPaint();
		}

		protected final void setFields(long fields)
		{
			int tmp;
			DateField thisItem;
			if((thisItem = (DateField) get()) == null)
			{
				return;
			}
			if((((int) (fields >> 32)) & 0xff) > (tmp = GregorianCalendar.getNumOfDays(
					(int) (fields >>> 48), ((int) (fields >> 40)) & 0xff)))
			{
				fields = (fields & ~DAY_MASK) | ((((long) tmp) << 32) & DAY_MASK);
			}
			if(thisItem.fields == fields)
			{
				return;
			}
			thisItem.fields = fields;
			thisItem.notifyPaint();
			thisItem.invokeNotifyStateChanged();
		}

		protected final long getFields()
		{
			DateField thisItem;
			return (thisItem = (DateField) get()) == null ? 0L : thisItem.fields;
		}
	}

	private static abstract class Integral extends Component
	{
		private boolean newInput;
		private int maxDigits;
		private int minValue;
		private int maxValue;
		private int shift;
		private long mask;

		public Integral(DateField thisItem, boolean oneBased, int maxValue, int shift)
		{
			super(thisItem);
			this.newInput = true;
			this.maxDigits = (maxValue &= 0xffff) < 10 ? 1 : (maxValue < 100 ? 2 :
					(maxValue < 1000 ? 3 : (maxValue < 10000 ? 4 : 5)));
			this.minValue = oneBased ? 1 : 0;
			this.maxValue = maxValue;
			this.shift = shift;
			this.mask = maxValue < 0x0100 ? 0x00ffL : 0xffffL;
		}

		public void paint(Graphics render, int width, int height,
				boolean focused, boolean pressed, long fields, String offset)
		{
			int lprev;
			int lval;
			int lnext;
			int t;
			int a;
			Font font = render.getFont();
			lval = (lprev = pressed ? 3 : 2) + font.charWidth('<');
			lnext = width - (4 - lprev) - font.charWidth('>');
			t = pressed ? 3 : 2;
			render.setColor(RasterCanvas.getSystemColor(
					(a = pressed ? 1 : (focused ? 3 : 0)) + 0x24));
			render.drawElementOfGUI(4, a, 0, 0, 0, width, height);
			render.drawChar('<', lprev, t, Graphics.LEFT | Graphics.TOP);
			render.drawChar('>', lnext, t, Graphics.LEFT | Graphics.TOP);
			render.translate(lval, t);
			paintValue(render, lnext - lval, font.getHeight(), (int) ((fields >> shift) & mask));
		}

		public void keyPressed(int keyCode)
		{
			int value;
			if(keyCode < Canvas.KEY_NUM0 || keyCode > Canvas.KEY_NUM9)
			{
				return;
			}
			if(newInput)
			{
				newInput = false;
				setValue(keyCode - Canvas.KEY_NUM0);
				return;
			}
			if(((value = getValue()) < 10 ? 1 : (value < 100 ? 2 :
					(value < 1000 ? 3 : (value < 10000 ? 4 : 5)))) >= maxDigits - 1)
			{
				newInput = true;
			}
			setValue((value * 10) + (keyCode - Canvas.KEY_NUM0));
		}

		public void pointerReleased(int x, int y)
		{
			int w;
			int value;
			newInput = true;
			if(y < 0 || y >= height)
			{
				return;
			}
			if(x < (w = width) / 2 && x >= 0)
			{
				setValue((value = getValue()) <= minValue ? maxValue : value - 1);
				return;
			}
			if(x < w && x >= 0)
			{
				setValue((value = getValue()) >= maxValue ? minValue : value + 1);
			}
		}

		public boolean traverse(int direction, boolean focused)
		{
			int value;
			if(direction != 0)
			{
				newInput = true;
			}
			if(!focused)
			{
				return true;
			}
			if(direction == Canvas.LEFT)
			{
				setValue((value = getValue()) <= minValue ? maxValue : value - 1);
				return true;
			}
			if(direction == Canvas.RIGHT)
			{
				setValue((value = getValue()) >= maxValue ? minValue : value + 1);
				return true;
			}
			return false;
		}

		public int getWidth()
		{
			Font font = FONT;
			return getValueMaxWidth() + font.charWidth('<') + font.charWidth('>') + 4;
		}

		public int getHeight()
		{
			return FONT.getHeight() + 4;
		}

		protected abstract void paintValue(Graphics render, int width, int height, int value);

		protected abstract int getValueMaxWidth();

		protected final void setValue(int value)
		{
			int tmp;
			int shift = this.shift;
			long mask = this.mask << shift;
			if(value < (tmp = minValue))
			{
				value = tmp;
			}
			if(value > (tmp = maxValue))
			{
				value = tmp;
			}
			setFields((getFields() & ~mask) | ((((long) value) << shift) & mask));
		}

		protected final int getValue()
		{
			return (int) ((getFields() >> shift) & mask);
		}

		protected final int getMaxDigits()
		{
			return maxDigits;
		}
	}

	private static class DigitalIntegral extends Integral
	{
		private char[] digits;

		public DigitalIntegral(DateField thisItem, boolean oneBased, int maxValue, int shift)
		{
			super(thisItem, oneBased, maxValue, shift);
			this.digits = new char[getMaxDigits()];
		}

		protected void paintValue(Graphics render, int width, int height, int value)
		{
			paintValue(render, width, height, value, digits);
		}

		protected int getValueMaxWidth()
		{
			return MAX_DIGIT_WIDTH * getMaxDigits();
		}

		protected void paintValue(Graphics render, int width, int height, int value, char[] digits)
		{
			int i;
			int len;
			for(i = len = digits.length; i-- > 0; )
			{
				digits[i] = (char) ((value % 10) + '0');
				if((value /= 10) == 0)
				{
					break;
				}
			}
			render.drawChars(digits, i, len - i, width / 2, 0, Graphics.HCENTER | Graphics.TOP);
		}
	}

	private static class DigitalIntegralWithLeadingZeros extends DigitalIntegral
	{
		public DigitalIntegralWithLeadingZeros(DateField thisItem,
				boolean oneBased, int maxValue, int shift)
		{
			super(thisItem, oneBased, maxValue, shift);
		}

		protected void paintValue(Graphics render, int width, int height, int value, char[] digits)
		{
			int i;
			int len;
			for(i = len = digits.length; i-- > 0; value /= 10)
			{
				digits[i] = (char) ((value % 10) + '0');
			}
			render.drawChars(digits, 0, len, width / 2, 0, Graphics.HCENTER | Graphics.TOP);
		}
	}

	private static class Month extends Integral
	{
		private static final String[] MONTHS;

		static
		{
			MONTHS = new String[] {
					"Январь", "Февраль", "Март", "Апрель", "Май", "Июнь",
					"Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь",
			};
		}


		public Month(DateField thisItem)
		{
			super(thisItem, true, MONTHS.length, 40);
		}

		protected void paintValue(Graphics render, int width, int height, int value)
		{
			int len = MONTHS.length;
			render.drawString(MONTHS[--value < 0 ? 0 : (value >= len ? len - 1 : value)],
					width / 2, 0, Graphics.HCENTER | Graphics.TOP);
		}

		protected int getValueMaxWidth()
		{
			int i;
			int tmp;
			int result = 0;
			Font font = FONT;
			for(i = MONTHS.length; i-- > 0; )
			{
				if(result < (tmp = font.stringWidth(MONTHS[i])))
				{
					result = tmp;
				}
			}
			return result;
		}
	}

	private static class Day extends Component
	{
		private static final String[] DAYS;

		static
		{
			DAYS = new String[] {
					"пн", "вт", "ср", "чт", "пт", "сб", "вс"
			};
		}

		private static int toInnerFormat(int dayOfWeek)
		{
			return dayOfWeek == 0 ? 6 : dayOfWeek - 1;
		}


		private int numOfDays;
		private int focusedDay;
		private int firstDayOfWeek;
		private char[] digits;
		
		public Day(DateField thisField)
		{
			super(thisField);
			this.focusedDay = 1;
			this.digits = new char[2];
		}

		public void paint(Graphics render, int width, int height,
				boolean focused, boolean pressed, long fields, String offset)
		{
			int i;
			int j;
			int l;
			int t;
			int w = width / 7;
			int h = height / 7;
			int nd;
			int sd;
			int ofs;
			int len;
			int year = (int) (fields >>> 48);
			int month = ((int) (fields >> 40)) & 0xff;
			int day = ((int) (fields >> 32)) & 0xff;
			int fday = focused ? focusedDay : 0;
			int color;
			int outday;
			char[] digs = digits;
			numOfDays = nd = GregorianCalendar.getNumOfDays(year, month);
			firstDayOfWeek = sd = toInnerFormat(GregorianCalendar.getDayOfWeek(year, month, 1));
			render.setColor(color = RasterCanvas.getSystemColor(0x28));
			for(l = 0, t = 0, i = 0; i < 7; l += w, i++)
			{
				render.drawString(DAYS[i], l + (w / 2), t + 2, Graphics.HCENTER | Graphics.TOP);
			}
			outday = 1 - sd;
			for(t = h, j = 0; j < 6; t += h, j++)
			{
				for(l = 0, i = 0; i < 7; l += w, outday++, i++)
				{
					if(outday < 1 || outday > nd)
					{
						continue;
					}
					render.setColor(outday == fday ? RasterCanvas.getSystemColor(0x27) :
							(outday != day ? color : RasterCanvas.getSystemColor(0x24)));
					if(outday == fday)
					{
						render.drawElementOfGUI(4, 3, 0, l, t, w, h);
					}
					else if(outday == day)
					{
						render.drawElementOfGUI(4, 0, 0, l, t, w, h);
					}
					digs[0] = (char) ((outday / 10) + '0');
					digs[1] = (char) ((outday % 10) + '0');
					if(outday < 10)
					{
						ofs = 1;
						len = 1;
					} else
					{
						ofs = 0;
						len = 2;
					}
					render.drawChars(digs, ofs, len, l + (w / 2), t + 2,
							Graphics.HCENTER | Graphics.TOP);
				}
			}
		}

		public void pointerPressed(int x, int y)
		{
			int fday;
			if((fday = getDayUnderPointer(x, y)) > 0 && focusedDay != fday)
			{
				focusedDay = fday;
				repaint();
			}
		}

		public void pointerDragged(int x, int y)
		{
			int fday;
			if((fday = getDayUnderPointer(x, y)) > 0 && focusedDay != fday)
			{
				focusedDay = fday;
				repaint();
			}
		}

		public void pointerReleased(int x, int y)
		{
			int fday;
			if((fday = getDayUnderPointer(x, y)) > 0)
			{
				focusedDay = fday;
				setFields((getFields() & ~DAY_MASK) | ((((long) fday) << 32) & DAY_MASK));
			}
		}

		public void select()
		{
			setFields((getFields() & ~DAY_MASK) | ((((long) focusedDay) << 32) & DAY_MASK));
		}

		public boolean hasSelectCommand()
		{
			return true;
		}

		public boolean traverse(int direction, boolean focused)
		{
			int f;
			switch(direction)
			{
			case Canvas.UP:
				if(focused)
				{
					focusedDay = f = focusedDay - 7;
					repaint();
					return f >= 1;
				}
				focusedDay = numOfDays;
				break;
			case Canvas.LEFT:
				if(focused)
				{
					focusedDay = f = focusedDay - 1;
					repaint();
					return f >= 1;
				}
				focusedDay = numOfDays;
				break;
			case Canvas.RIGHT:
				if(focused)
				{
					focusedDay = f = focusedDay + 1;
					repaint();
					return f <= numOfDays;
				}
				focusedDay = 1;
				break;
			case Canvas.DOWN:
				if(focused)
				{
					focusedDay = f = focusedDay + 7;
					repaint();
					return f <= numOfDays;
				}
				focusedDay = 1;
				break;
			}
			return true;
		}

		public int getWidth()
		{
			return (2 * MAX_DIGIT_WIDTH + 4) * 7;
		}

		public int getHeight()
		{
			return (FONT.getHeight() + 4) * 7;
		}

		private int getDayUnderPointer(int x, int y)
		{
			int w = Math.max(1, width / 7);
			int h = Math.max(1, height / 7);
			int col = x / w;
			int row = y / h;
			int result;
			return (result = 7 * row + col + (-6 - firstDayOfWeek)) < 1 ||
					result > numOfDays ? 0 : result;
		}
	}

	private static class Offset extends Component
	{
		public Offset(DateField thisField)
		{
			super(thisField);
		}

		public void paint(Graphics render, int width, int height,
				boolean focused, boolean pressed, long fields, String offset)
		{
			int l;
			int t;
			int a;
			l = pressed ? 3 : 2;
			t = pressed ? 3 : 2;
			render.setColor(RasterCanvas.getSystemColor(
					(a = pressed ? 1 : (focused ? 3 : 0)) + 0x24));
			render.drawElementOfGUI(4, a, 0, 0, 0, width, height);
			render.drawSubstring(offset, 0, 9, l, t, Graphics.LEFT | Graphics.TOP);
		}

		public void pointerReleased(int x, int y)
		{
			if(x >= 0 && x < width && y >= 0 && y < height)
			{
				select();
			}
		}

		public void select()
		{
			DateField thisItem;
			if((thisItem = (DateField) get()) == null)
			{
				return;
			}
			thisItem.showTimeZonesList();
		}

		public boolean hasSelectCommand()
		{
			return true;
		}

		public int getWidth()
		{
			Font font = FONT;
			return font.stringWidth("UTC+") + 4 * MAX_DIGIT_WIDTH + font.charWidth(':') + 4;
		}

		public int getHeight()
		{
			return FONT.getHeight() + 4;
		}
	}

	public static final int DATE = 1;
	public static final int TIME = 2;
	public static final int DATE_TIME = 3;
	private static final int MONTH = 0;
	private static final int YEAR = 1;
	private static final int DAY = 2;
	private static final int HOUR = 3;
	private static final int MINUTE = 4;
	private static final int OFFSET = 5;
	private static final int ZERO_OFFSET_INDEX;
	private static final int MAX_DIGIT_WIDTH;
	private static final int[] OFFSETS;
	private static final String[] NAMES;
	private static final Command BACK;
	private static final Command SELECT;
	private static final Font FONT;

	static
	{
		char j;
		int w;
		int i;
		int len;
		int ofs;
		int tmp;
		int zeroOffsetIndex;
		int[] offsets;
		String[] names;
		String s;
		Font f;
		names = new String[] {
				"UTC-12:00", "UTC-11:00", "UTC-10:00", "UTC-09:30", "UTC-09:00",
				"UTC-08:30*", "UTC-08:00", "UTC-07:00", "UTC-06:00", "UTC-05:00",
				"UTC-04:30", "UTC-04:00", "UTC-03:30", "UTC-03:00", "UTC-02:30",
				"UTC-02:00", "UTC-01:00", "UTC-00:44*", "UTC-00:25*", "UTC+00:00",
				"UTC+00:20*", "UTC+00:30*", "UTC+01:00", "UTC+02:00", "UTC+03:00",
				"UTC+03:30", "UTC+04:00", "UTC+04:30", "UTC+04:51*", "UTC+05:00",
				"UTC+05:30", "UTC+05:40*", "UTC+05:45", "UTC+06:00", "UTC+06:30",
				"UTC+07:00", "UTC+07:20*", "UTC+07:30*", "UTC+08:00", "UTC+08:30",
				"UTC+08:45", "UTC+09:00", "UTC+09:30", "UTC+10:00", "UTC+10:30",
				"UTC+11:00", "UTC+11:30*", "UTC+12:00", "UTC+12:45", "UTC+13:00",
				"UTC+13:45", "UTC+14:00",
		};
		zeroOffsetIndex = -1;
		offsets = new int[len = names.length];
		for(i = len; i-- > 0; )
		{
			if((s = names[i]).length() < 9)
			{
				continue;
			}
			ofs = (s.charAt(4) - '0') * 36000000 + (s.charAt(5) - '0') * 3600000 +
					(s.charAt(7) - '0') * 600000 + (s.charAt(8) - '0') * 60000;
			offsets[i] = s.charAt(3) == '-' ? -ofs : ofs;
			if(ofs == 0 && zeroOffsetIndex < 0)
			{
				zeroOffsetIndex = i;
			}
		}
		if(zeroOffsetIndex < 0)
		{
			zeroOffsetIndex = 0;
		}
		w = (f = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL)).charWidth('0');
		for(j = '1'; j <= '9'; j++)
		{
			if(w < (tmp = f.charWidth(j)))
			{
				w = tmp;
			}
		}
		ZERO_OFFSET_INDEX = zeroOffsetIndex;
		MAX_DIGIT_WIDTH = w;
		OFFSETS = offsets;
		NAMES = names;
		BACK = new Command("Назад", Command.BACK, 0);
		SELECT = new Command("Выбрать", Command.OK, 0);
		FONT = f;
	}

	private static int indexOfOffset(int offset)
	{
		int i;
		for(i = OFFSETS.length; i-- > 0; )
		{
			if(OFFSETS[i] == offset)
			{
				return i;
			}
		}
		return ZERO_OFFSET_INDEX;
	}


	private boolean controlFocused;
	private boolean componentFocused;
	private boolean needPlaceComponents;
	private int contentWidth;
	private int contentHeight;
	private int focused;
	private int pressed;
	private int mode;
	private int offset;
	private long fields;
	private Component[] components;
	private List timeZonesList;
	private Displayable sourceScreen;

	public DateField(String label, int mode)
	{
		this(LAYOUT_NEWLINE_AFTER, label, null, null, null, mode, null, null);
	}

	public DateField(String label, int mode, TimeZone zone)
	{
		this(LAYOUT_NEWLINE_AFTER, label, null, null, null, mode, null, zone);
	}

	public DateField(int layout, String label,
			Command[] commands, Command defaultCommand, ItemCommandListener listener,
			int mode, Date time, TimeZone zone)
	{
		super(layout, -1, -1, label, commands, defaultCommand, listener);
		int ofs;
		Command[] timeZonesCommands;
		if(mode < DATE || mode > DATE_TIME)
		{
			throw new IllegalArgumentException("DateField: " +
					"недопустимое значение параметра mode.");
		}
		timeZonesCommands = new Command[] {
				BACK
		};
		this.needPlaceComponents = true;
		this.focused = -1;
		this.pressed = -1;
		this.mode = mode;
		this.offset = ofs = indexOfOffset((
				zone == null ? ListOfTimeZones.getDefault() : zone).getRawOffset());
		this.fields = GregorianCalendar.computeFields(
				time == null ? System.currentTimeMillis() : time.getTime(), OFFSETS[ofs]);
		this.components = new Component[] {
				new Month(this),
				new DigitalIntegral(this, true, 0xffff, 48),
				new Day(this),
				new DigitalIntegralWithLeadingZeros(this, false, 23, 24),
				new DigitalIntegralWithLeadingZeros(this, false, 59, 16),
				new Offset(this)
		};
		this.timeZonesList = new List(false, "Часовой пояс (* – исторический):", null,
				timeZonesCommands, List.SELECT_COMMAND, new TimeZonesListCommandHandler(this),
				Choice.IMPLICIT, NAMES, null);
	}

	public void setInputMode(int mode)
	{
		if(mode < DATE || mode > DATE_TIME)
		{
			throw new IllegalArgumentException("DateField.setInputMode: " +
					"недопустимое значение параметра mode.");
		}
		this.needPlaceComponents = true;
		this.mode = mode;
		notifyUpdate();
	}

	public void setDate(Date time)
	{
		this.fields = GregorianCalendar.computeFields(
				time == null ? System.currentTimeMillis() : time.getTime(), OFFSETS[offset]);
		notifyPaint();
	}

	public int getInputMode()
	{
		return mode;
	}

	public Date getDate()
	{
		int tz;
		long f;
		switch(mode)
		{
		case DATE:
			tz = 0;
			f = fields & ~0xffffffffL;
			break;
		case TIME:
			tz = 0;
			f = ((1970L << 48) | (1L << 40) | (1L << 32)) | (fields & 0xffff0000L);
			break;
		case DATE_TIME:
			tz = OFFSETS[offset];
			f = fields & ~0xffffL;
			break;
		default:
			tz = 0;
			f = 0L;
			break;
		}
		return new Date(GregorianCalendar.computeTime(f, tz));
	}

	void paintContent(Graphics render, int contentWidth, int contentHeight)
	{
		boolean cfocused = controlFocused;
		int i;
		int w;
		int h;
		int f = focused;
		int p = pressed;
		int tx = render.getTranslateX();
		int ty = render.getTranslateY();
		long fields = this.fields;
		Component[] list;
		Component comp;
		String offset = NAMES[this.offset];
		for(i = (list = components).length; i-- > 0; )
		{
			if(!(comp = list[i]).visible)
			{
				continue;
			}
			render.translate(
					tx + comp.left - render.getTranslateX(),
					ty + comp.top - render.getTranslateY());
			render.setFont(FONT);
			w = comp.width;
			h = comp.height;
			if(i == HOUR)
			{
				render.setColor(RasterCanvas.getSystemColor(0x27));
				render.setClip(0, 0, w + FONT.charWidth(':') + 2, h);
				render.drawChar(':', w + 1, 2, Graphics.LEFT | Graphics.TOP);
			}
			render.setClip(0, 0, w, h);
			comp.paint(render, w, h, cfocused && i == f, cfocused && i == p, fields, offset);
		}
	}

	void onKeyPressed(int key, int charCode)
	{
		int f;
		Component[] list;
		if((f = focused) >= 0 && f < (list = components).length)
		{
			list[f].keyPressed(MIDletProxy.getInstance().getKeyCode(key));
		}
	}

	void onContentPointerPressed(int x, int y, int button)
	{
		int f;
		int next;
		Component comp;
		if(button == MIDletProxy.BUTTON_MAIN && (next = getComponentIndexUnderPointer(x, y)) >= 0)
		{
			f = focused;
			componentFocused = true;
			focus(next, true);
			(comp = components[next]).traverse(0, f == next);
			comp.pointerPressed(x - comp.left, y - comp.top);
			notifyPaint();
		}
	}

	void onContentPointerDragged(int x, int y)
	{
		int f;
		Component comp;
		if(componentFocused)
		{
			(comp = components[f = focused]).pointerDragged(x - comp.left, y - comp.top);
			if(pressed != (pressed = f == getComponentIndexUnderPointer(x, y) ? f : -1))
			{
				notifyPaint();
			}
		}
	}

	void onContentPointerReleased(int x, int y, int button)
	{
		Component comp;
		if(button == MIDletProxy.BUTTON_MAIN && componentFocused)
		{
			pressed = -1;
			componentFocused = false;
			(comp = components[focused]).pointerReleased(x - comp.left, y - comp.top);
			notifyPaint();
		}
	}

	void onCommandAction(Command command)
	{
		if(command == SELECT)
		{
			components[focused].select();
			return;
		}
		super.onCommandAction(command);
	}

	void onTraverseOut()
	{
		controlFocused = false;
		notifyPaint();
	}

	boolean onTraverseNeedStayOn(int direction, int viewportWidth, int viewportHeight,
			int[] visibleRectangle)
	{
		boolean cfocused;
		int labelHeight;
		int f;
		int i;
		int tmp;
		int cmo;
		int cms;
		int vro;
		int vrs;
		int len;
		Component[] list;
		Component comp;
		if((cfocused = controlFocused) != (controlFocused = true))
		{
			notifyPaint();
		}
		if(componentFocused)
		{
			return true;
		}
		len = (list = components).length;
		label0:
		{
			switch(direction)
			{
			case Canvas.UP:
			case Canvas.LEFT:
				if(!cfocused)
				{
					for(i = len; i-- > 0; )
					{
						if(list[i].visible)
						{
							focus(f = i, false);
							break label0;
						}
					}
				}
				break;
			case Canvas.RIGHT:
			case Canvas.DOWN:
				if(!cfocused)
				{
					for(i = 0; i < len; i++)
					{
						if(list[i].visible)
						{
							focus(f = i, false);
							break label0;
						}
					}
				}
				break;
			}
			if(cfocused && (i = focused) >= 0 && i < len)
			{
				if(list[i].traverse(direction, true))
				{
					return true;
				}
				switch(direction)
				{
				case Canvas.UP:
				case Canvas.LEFT:
					while(i-- > 0)
					{
						if(list[i].visible)
						{
							focus(f = i, false);
							break label0;
						}
					}
					break;
				case Canvas.RIGHT:
				case Canvas.DOWN:
					for(i++; i < len; i++)
					{
						if(list[i].visible)
						{
							focus(f = i, false);
							break label0;
						}
					}
					break;
				}
			}
			return false;
		}
		(comp = list[f]).traverse(direction, false);
		labelHeight = super.getPreferredHeight() - contentHeight;
		if((vrs = visibleRectangle[WIDTH]) < (cms = comp.width))
		{
			visibleRectangle[WIDTH] = vrs = cms;
		}
		if((vro = visibleRectangle[LEFT]) > (tmp = (cmo = comp.left)))
		{
			visibleRectangle[LEFT] = tmp;
		}
		else if(vro < (tmp = cmo + cms - vrs))
		{
			visibleRectangle[LEFT] = tmp;
		}
		if((vrs = visibleRectangle[HEIGHT]) < (cms = comp.height))
		{
			visibleRectangle[HEIGHT] = vrs = cms;
		}
		if((vro = visibleRectangle[TOP]) > (tmp = labelHeight + (cmo = comp.top)))
		{
			visibleRectangle[TOP] = tmp;
		}
		else if(vro < (tmp = labelHeight + cmo + cms - vrs))
		{
			visibleRectangle[TOP] = tmp;
		}
		notifyPaint();
		return true;
	}

	boolean keyHandling(int key)
	{
		int keyCode;
		return (keyCode = MIDletProxy.getInstance().getKeyCode(key)) >= Canvas.KEY_NUM0 &&
				keyCode <= Canvas.KEY_NUM9;
	}

	int getPreferredContentWidth(int contentHeight, int containerClientWidth)
	{
		if(needPlaceComponents)
		{
			needPlaceComponents = false;
			placeComponents();
		}
		return contentWidth;
	}

	int getPreferredContentHeight(int contentWidth)
	{
		if(needPlaceComponents)
		{
			needPlaceComponents = false;
			placeComponents();
		}
		return contentHeight;
	}

	private void showTimeZonesList()
	{
		List screen = timeZonesList;
		if((sourceScreen = getOwner()) != null)
		{
			screen.setSelectedIndex(offset, true);
			MIDletProxy.getInstance().getEmulatorScreen().setCurrent(screen);
		}
	}

	private void showSourceScreen()
	{
		Displayable next;
		Display display = MIDletProxy.getInstance().getEmulatorScreen();
		if((next = sourceScreen).isSystem())
		{
			display.setCurrent(next);
		} else
		{
			display.hideSystemScreen();
		}
		sourceScreen = null;
	}

	private void invokeNotifyStateChanged()
	{
		super.notifyStateChanged();
	}

	private void placeComponents()
	{
		boolean vis;
		int l;
		int t;
		int r;
		int b;
		int i;
		int len;
		int maxWidth;
		int maxHeight;
		Component[] list;
		Component prev;
		Component comp;
		maxWidth = 0;
		maxHeight = 0;
		len = (list = components).length;
		switch(mode)
		{
		case DATE:
			for(prev = null, i = 0; i < len; prev = comp, i++)
			{
				comp = list[i];
				switch(i)
				{
				case MONTH:
					comp.visible = vis = true;
					comp.left = l = 0;
					comp.top = t = 0;
					break;
				case YEAR:
					comp.visible = vis = true;
					comp.left = l = prev.left + prev.width;
					comp.top = t = prev.top;
					break;
				case DAY:
					comp.visible = vis = true;
					comp.left = l = 0;
					comp.top = t = prev.top + prev.height;
					break;
				default:
					comp.visible = vis = false;
					l = 0;
					t = 0;
					break;
				}
				if(vis)
				{
					r = l + (comp.width = comp.getWidth());
					b = t + (comp.height = comp.getHeight());
					if(maxWidth < r)
					{
						maxWidth = r;
					}
					if(maxHeight < b)
					{
						maxHeight = b;
					}
				}
			}
			(comp = list[YEAR]).left = maxWidth - comp.width;
			break;
		case TIME:
			for(prev = null, i = 0; i < len; prev = comp, i++)
			{
				comp = list[i];
				switch(i)
				{
				case HOUR:
					comp.visible = vis = true;
					comp.left = l = 0;
					comp.top = t = 0;
					break;
				case MINUTE:
					comp.visible = vis = true;
					comp.left = l = prev.left + prev.width + FONT.charWidth(':') + 2;
					comp.top = t = prev.top;
					break;
				default:
					comp.visible = vis = false;
					l = 0;
					t = 0;
					break;
				}
				if(vis)
				{
					r = l + (comp.width = comp.getWidth());
					b = t + (comp.height = comp.getHeight());
					if(maxWidth < r)
					{
						maxWidth = r;
					}
					if(maxHeight < b)
					{
						maxHeight = b;
					}
				}
			}
			break;
		case DATE_TIME:
			for(prev = null, i = 0; i < len; prev = comp, i++)
			{
				(comp = list[i]).visible = true;
				switch(i)
				{
				case MONTH:
					comp.left = l = 0;
					comp.top = t = 0;
					break;
				case DAY:
				case HOUR:
					comp.left = l = 0;
					comp.top = t = prev.top + prev.height;
					break;
				case MINUTE:
					comp.left = l = prev.left + prev.width + FONT.charWidth(':') + 2;
					comp.top = t = prev.top;
					break;
				default:
					comp.left = l = prev.left + prev.width;
					comp.top = t = prev.top;
					break;
				}
				r = l + (comp.width = comp.getWidth());
				b = t + (comp.height = comp.getHeight());
				if(maxWidth < r)
				{
					maxWidth = r;
				}
				if(maxHeight < b)
				{
					maxHeight = b;
				}
			}
			(comp = list[YEAR]).left = maxWidth - comp.width;
			(comp = list[OFFSET]).left = maxWidth - comp.width;
			break;
		}
		contentWidth = maxWidth;
		contentHeight = maxHeight;
	}

	private void focus(int componentIndex, boolean fromPointer)
	{
		boolean oldHasSelectCommand;
		boolean newHasSelectCommand;
		int len;
		int f;
		Component[] list;
		len = (list = components).length;
		oldHasSelectCommand = (f = focused) >= 0 && f < len && list[f].hasSelectCommand();
		newHasSelectCommand = (f = componentIndex) >= 0 && f < len && list[f].hasSelectCommand();
		focused = componentIndex;
		if(fromPointer)
		{
			pressed = componentIndex;
		}
		if(oldHasSelectCommand && !newHasSelectCommand)
		{
			super.removeCommand(SELECT);
		}
		if(newHasSelectCommand && !oldHasSelectCommand)
		{
			super.setDefaultCommand(SELECT);
		}
	}

	private int getComponentIndexUnderPointer(int x, int y)
	{
		int i;
		int tmp;
		Component[] list;
		Component comp;
		for(i = (list = components).length; i-- > 0; )
		{
			if((comp = list[i]).visible &&
					x >= (tmp = comp.left) && x < tmp + comp.width &&
					y >= (tmp = comp.top) && y < tmp + comp.height)
			{
				return i;
			}
		}
		return -1;
	}
}
