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

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

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

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

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

package javax.microedition.lcdui;

import java.util.*;
import malik.emulator.application.*;
import malik.emulator.media.graphics.*;
import malik.emulator.microedition.lcdui.*;
import malik.emulator.time.*;

public class DateField extends InteractiveItem
{
    public static final int DATE = 1;
    public static final int TIME = 2;
    public static final int DATE_TIME = 3;
    private static final int FIELD_YEAR = 0x0020ffff;
    private static final int FIELD_MONTH = 0x001800ff;
    private static final int FIELD_DAY = 0x001000ff;
    private static final int FIELD_HOUR = 0x000800ff;
    private static final int FIELD_MINUTE = 0x000000ff;

    private static final int OFFSET_ZERO_INDEX;
    private static final int[] OFFSET_VALUE;
    static final String[] OFFSET_NAMES;
    static final String[] DAY_NAMES;
    static final Command SELECT;
    static final Command BACK;
    static final Font FONT;

    static {
        int len;
        int zeroOffsetIndex;
        int[] offsetValue;
        String[] offsetNames = 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;
        offsetValue = new int[len = offsetNames.length];
        for(int i = len; i-- > 0; )
        {
            int offset;
            String s;
            if((s = offsetNames[i]) == null || s.length() < 9) continue;
            offset = (s.charAt(4) - '0') * 36000000 + (s.charAt(5) - '0') * 3600000 + (s.charAt(7) - '0') * 600000 + (s.charAt(8) - '0') * 60000;
            if(s.charAt(3) == '-') offset = -offset;
            if(offset == 0 && zeroOffsetIndex < 0) zeroOffsetIndex = i;
            offsetValue[i] = offset;
        }
        OFFSET_ZERO_INDEX = zeroOffsetIndex;
        OFFSET_VALUE = offsetValue;
        OFFSET_NAMES = offsetNames;
        DAY_NAMES = new String[] { "пн", "вт", "ср", "чт", "пт", "сб", "вс" };
        SELECT = new Command("Выбрать", Command.OK, Integer.MIN_VALUE);
        BACK = new Command("Назад", Command.BACK, 0);
        FONT = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
    }

    private static void setLeft(Component[] list, int offset, int length, int startLeft) {
        for(int lim = offset + length, i = offset; i < lim; i++)
        {
            Component cmp;
            (cmp = list[i]).left = startLeft;
            startLeft += cmp.width;
        }
    }

    private static void setTop(Component[] list, int offset, int length, int rowHeight, int baseTop) {
        for(int i = offset + length; i-- > offset; )
        {
            Component cmp;
            (cmp = list[i]).top = baseTop + ((rowHeight - cmp.height) >> 1);
        }
    }

    private static int indexOf(int offset) {
        int[] offsetValue;
        for(int i = (offsetValue = OFFSET_VALUE).length; i-- > 0; ) if(offsetValue[i] == offset) return i;
        return OFFSET_ZERO_INDEX;
    }

    private static int getOffset(int index) {
        return OFFSET_VALUE[index];
    }

    private static int getWidth(Component[] list, int offset, int length) {
        int result = 0;
        for(int i = offset + length; i-- > offset; result += list[i].width);
        return result;
    }

    private static int getHeight(Component[] list, int offset, int length) {
        int result = 0;
        for(int i = offset + length; i-- > offset; )
        {
            int height;
            if(result < (height = list[i].height)) result = height;
        }
        return result;
    }

    private static int getField(long fieldset, int field) {
        return (int) (fieldset >> (field >> 16)) & field & 0xffff;
    }

    private static long setField(long fieldset, int field, int value) {
        int shift = field >> 16;
        int mask = field & 0xffff;
        return fieldset & ~((long) mask << shift) | ((long) value & mask) << shift;
    }

    private abstract class Component extends Object
    {
        boolean visible;
        int left;
        int top;
        int width;
        int height;
        final int field;

        protected Component() {
            this.field = 0;
        }

        protected Component(int field) {
            this.field = field;
        }

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

        public abstract int getWidth();

        public abstract int getHeight();

        public void keyboardEvent(KeyboardEvent event) {
        }

        public void pointerEvent(PointerEvent event) {
        }

        public void selectEvent() {
        }

        public boolean hasSelectCommand() {
            return false;
        }

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

        public final void writeRectangle(int[] rect) {
            rect[0] = left;
            rect[1] = top;
            rect[2] = width;
            rect[3] = height;
        }

        public final void showTimeZoneList() {
            (DateField.this).showTimeZoneList();
        }

        public final void repaint() {
            (DateField.this).requestPaint();
        }

        public final void setFieldValue(int value) {
            (DateField.this).setFieldValue(field, value);
        }

        public final int getFieldValue() {
            return (DateField.this).getFieldValue(field);
        }

        public final int getFieldValue(int field) {
            return (DateField.this).getFieldValue(field);
        }
    }

    private class NumericComponent extends Component
    {
        private boolean newInput;
        protected final int minValue;
        protected final int maxValue;

        public NumericComponent(int field, int minValue, int maxValue) {
            (DateField.this).super(field);
            this.newInput = true;
            this.minValue = minValue;
            this.maxValue = maxValue;
        }

        public void paint(Graphics render, int width, int height, boolean focused, boolean pressed, int value, String offset) {
            int x = focused && pressed ? width - 2 : width - 3;
            int y = focused && pressed ? 4 : 3;
            render.setFont(DateField.FONT);
            render.setColor(RasterCanvas.getSystemColor(focused ? pressed ? 0x25 : 0x27 : 0x24));
            render.drawElement(4, focused ? pressed ? 1 : 3 : 0, 0, 0, 0, width, height);
            render.drawString(Integer.toString(value).concat("\u2195"), x, y, Graphics.RIGHT | Graphics.TOP);
        }

        public void keyboardEvent(KeyboardEvent event) {
            int digit;
            if((digit = event.getCharCode() - '0') >= 0 && digit <= 9)
            {
                long minValue = (long) this.minValue;
                long maxValue = (long) this.maxValue;
                long newValue;
                if(newInput)
                {
                    newInput = false;
                    if((newValue = (long) digit) < minValue) newValue = minValue;
                    if(newValue > maxValue) newValue = maxValue;
                }
                else if((newValue = (long) getFieldValue() * 10L + digit) > maxValue)
                {
                    newInput = true;
                    newValue = maxValue;
                }
                setFieldValue((int) newValue);
            }
        }

        public void pointerEvent(PointerEvent event) {
            int x;
            int y;
            int h;
            if(event.getAction() == PointerEvent.ACTION_POINTER_RELEASED && (x = event.getX()) >= 0 && x < width && (y = event.getY()) >= 0 && y < (h = height))
            {
                if(y >= (h >> 1))
                {
                    decreaseFieldValue();
                } else
                {
                    increaseFieldValue();
                }
            }
        }

        public boolean focusMove(int direction, boolean focused) {
            if(!focused) return newInput = true;
            switch(direction)
            {
            case Item.DIR_NONE:
                break;
            case Item.DIR_UP:
                newInput = true;
                increaseFieldValue();
                break;
            case Item.DIR_DOWN:
                newInput = true;
                decreaseFieldValue();
                break;
            default:
                return false;
            }
            return true;
        }

        public int getWidth() {
            int result = 0;
            int digits = 1;
            Font font = DateField.FONT;
            for(int val = maxValue; val > 9; val /= 10) digits++;
            for(char digit = '0'; digit <= '9'; digit++)
            {
                int width;
                if(result < (width = font.charWidth(digit))) result = width;
            }
            return result * digits + font.charWidth('\u2195') + 6;
        }

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

        public final void increaseFieldValue() {
            int curValue;
            setFieldValue((curValue = getFieldValue()) >= maxValue ? minValue : curValue + 1);
        }

        public final void decreaseFieldValue() {
            int curValue;
            setFieldValue((curValue = getFieldValue()) <= minValue ? maxValue : curValue - 1);
        }
    }

    private class TextNumericComponent extends NumericComponent
    {
        private final String[] strings;

        public TextNumericComponent(int field, int minValue, String[] strings) {
            (DateField.this).super(field, minValue, minValue + strings.length - 1);
            this.strings = strings;
        }

        public void paint(Graphics render, int width, int height, boolean focused, boolean pressed, int value, String offset) {
            int x = focused && pressed ? width - 2 : width - 3;
            int y = focused && pressed ? 4 : 3;
            render.setFont(DateField.FONT);
            render.setColor(RasterCanvas.getSystemColor(focused ? pressed ? 0x25 : 0x27 : 0x24));
            render.drawElement(4, focused ? pressed ? 1 : 3 : 0, 0, 0, 0, width, height);
            render.drawString(strings[value - minValue], x, y, Graphics.RIGHT | Graphics.TOP);
        }

        public int getWidth() {
            int result = 0;
            String[] strings = this.strings;
            Font font = DateField.FONT;
            for(int i = strings.length; i-- > 0; )
            {
                int width;
                if(result < (width = font.stringWidth(strings[i]))) result = width;
            }
            return result + 6;
        }
    }

    private class StringComponent extends Component
    {
        private final Font font;
        private final String text;

        public StringComponent(String text, Font font) {
            (DateField.this).super();
            this.font = font == null ? Font.getDefaultFont() : font;
            this.text = text == null ? "" : text;
        }

        public void paint(Graphics render, int width, int height, boolean focused, boolean pressed, int value, String offset) {
            render.setFont(font);
            render.setColor(RasterCanvas.getSystemColor(0x28));
            render.drawString(text, 0, 0, Graphics.LEFT | Graphics.TOP);
        }

        public int getWidth() {
            return font.stringWidth(text);
        }

        public int getHeight() {
            return font.getHeight();
        }
    }

    private class DaySelectComponent extends Component
    {
        private int selectedDay;

        public DaySelectComponent() {
            (DateField.this).super(DateField.FIELD_DAY);
        }

        public void paint(Graphics render, int width, int height, boolean focused, boolean pressed, int day, String offset) {
            int dx = width / 7;
            int dy = height / 7;
            int daySelected = focused ? selectedDay : 0;
            int year = getFieldValue(DateField.FIELD_YEAR);
            int month = getFieldValue(DateField.FIELD_MONTH);
            int firstWeekDay = CalendarSystem.gregorian.computeDayOfWeek(year, month, 1);
            int numberOfDays = CalendarSystem.gregorian.computeNumberOfDays(year, month);
            String[] days = DateField.DAY_NAMES;
            Font font = DateField.FONT;
            render.setFont(font);
            render.setColor(RasterCanvas.getSystemColor(0x28));
            for(int x = dx >> 1, i = 0; i < 7; x += dx, i++) render.drawString(days[i], x, 3, Graphics.HCENTER | Graphics.TOP);
            if(firstWeekDay == 0) firstWeekDay = 7;
            for(int dayCurrent = 2 - firstWeekDay, y = dy, i = 0; i < 6; y += dy, i++) for(int x = 0, j = 0; j < 7; dayCurrent++, x += dx, j++) if(dayCurrent >= 1 && dayCurrent <= numberOfDays)
            {
                if(dayCurrent == daySelected)
                {
                    render.setColor(RasterCanvas.getSystemColor(focused ? pressed ? 0x25 : 0x27 : 0x24));
                    render.drawElement(4, focused ? pressed ? 1 : 3 : 0, 0, x, y, dx, dy);
                    render.drawString(Integer.toString(dayCurrent), x + (dx >> 1) + (focused && pressed ? 1 : 0), y + (focused && pressed ? 4 : 3), Graphics.HCENTER | Graphics.TOP);
                    continue;
                }
                if(dayCurrent == day)
                {
                    render.drawElement(4, 0, 0, x, y, dx, dy);
                    render.setColor(RasterCanvas.getSystemColor(0x24));
                } else
                {
                    render.setColor(RasterCanvas.getSystemColor(0x28));
                }
                render.drawString(Integer.toString(dayCurrent), x + (dx >> 1), y + 3, Graphics.HCENTER | Graphics.TOP);
            }
        }

        public void pointerEvent(PointerEvent event) {
            int x = event.getX();
            int y = event.getY();
            int dx = width;
            int dy = height;
            if(x >= 0 && x < dx && y >= 0 && y < dy)
            {
                int col;
                int row;
                dx /= 7;
                dy /= 7;
                col = x / dx;
                row = y / dy;
                if(row-- > 0)
                {
                    int day;
                    int year = getFieldValue(DateField.FIELD_YEAR);
                    int month = getFieldValue(DateField.FIELD_MONTH);
                    int firstWeekDay = CalendarSystem.gregorian.computeDayOfWeek(year, month, 1);
                    int numberOfDays = CalendarSystem.gregorian.computeNumberOfDays(year, month);
                    if(firstWeekDay == 0) firstWeekDay = 7;
                    if((day = 2 - firstWeekDay + row * 7 + col) < 1)
                    {
                        selectedDay = 1;
                        repaint();
                    }
                    else if(day > numberOfDays)
                    {
                        selectedDay = numberOfDays;
                        repaint();
                    }
                    else
                    {
                        selectedDay = day;
                        if(event.getAction() == PointerEvent.ACTION_POINTER_RELEASED)
                        {
                            setFieldValue(day);
                        } else
                        {
                            repaint();
                        }
                    }
                }
            }
        }

        public void selectEvent() {
            setFieldValue(selectedDay);
        }

        public boolean hasSelectCommand() {
            return true;
        }

        public boolean focusMove(int direction, boolean focused) {
            int daySelected = selectedDay;
            int year = getFieldValue(DateField.FIELD_YEAR);
            int month = getFieldValue(DateField.FIELD_MONTH);
            int numberOfDays = CalendarSystem.gregorian.computeNumberOfDays(year, month);
            if(!focused)
            {
                switch(direction)
                {
                default:
                    selectedDay = 1;
                    break;
                case Item.DIR_UP:
                case Item.DIR_LEFT:
                    selectedDay = numberOfDays;
                    break;
                }
                return true;
            }
            switch(direction)
            {
            default:
                return true;
            case Item.DIR_LEFT:
                if(--daySelected < 1) return false;
                break;
            case Item.DIR_RIGHT:
                if(++daySelected > numberOfDays) return false;
                break;
            case Item.DIR_UP:
                if((daySelected -= 7) < 1) return false;
                break;
            case Item.DIR_DOWN:
                if((daySelected += 7) > numberOfDays) return false;
                break;
            }
            selectedDay = daySelected;
            repaint();
            return true;
        }

        public int getWidth() {
            int result = 0;
            Font font = DateField.FONT;
            for(char digit = '0'; digit <= '9'; digit++)
            {
                int width;
                if(result < (width = font.charWidth(digit))) result = width;
            }
            return (2 * result + 6) * 7;
        }

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

    private class TimeZoneComponent extends Component
    {
        public TimeZoneComponent() {
            (DateField.this).super();
        }

        public void paint(Graphics render, int width, int height, boolean focused, boolean pressed, int value, String offset) {
            int x = focused && pressed ? (width >> 1) + 1 : (width >> 1);
            int y = focused && pressed ? 4 : 3;
            render.setFont(DateField.FONT);
            render.setColor(RasterCanvas.getSystemColor(focused ? pressed ? 0x25 : 0x27 : 0x24));
            render.drawElement(4, focused ? pressed ? 1 : 3 : 0, 0, 0, 0, width, height);
            render.drawString(offset, x, y, Graphics.HCENTER | Graphics.TOP);
        }

        public void pointerEvent(PointerEvent event) {
            int x;
            int y;
            if(event.getAction() == PointerEvent.ACTION_POINTER_RELEASED && (x = event.getX()) >= 0 && x < width && (y = event.getY()) >= 0 && y < height) showTimeZoneList();
        }

        public void selectEvent() {
            showTimeZoneList();
        }

        public boolean hasSelectCommand() {
            return true;
        }

        public boolean focusMove(int direction, boolean focused) {
            return !focused || direction == Item.DIR_NONE;
        }

        public int getWidth() {
            int result = 0;
            String[] names = DateField.OFFSET_NAMES;
            Font font = DateField.FONT;
            for(int i = names.length; i-- > 0; )
            {
                int width;
                if(result < (width = font.stringWidth(names[i]))) result = width;
            }
            return result + 6;
        }

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

    private boolean needPlaceComponents;
    private int mode;
    private int width;
    private int height;
    private int current;
    private int focused;
    private int pressed;
    private int ofsindex;
    private long fieldset;
    private final Component[] components;
    private final StringList timeZoneList;
    private final Object monitor;

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

    public DateField(String label, int mode, TimeZone zone) {
        super(label);
        int index;
        long fields;
        long fieldset;
        Component[] list;
        CalendarSystem system;
        StringList timeZoneList;
        if(mode < DATE || mode > DATE_TIME)
        {
            throw new IllegalArgumentException("DateField: аргумент mode имеет недопустимое значение.");
        }
        index = indexOf((zone == null ? TimeZone.getDefault() : zone).getRawOffset());
        fields = (system = CalendarSystem.gregorian).computeFields(System.currentTimeMillis(), DateField.getOffset(index));
        fieldset = setField(0L, FIELD_YEAR, system.getYear(fields));
        fieldset = setField(fieldset, FIELD_MONTH, system.getMonth(fields));
        fieldset = setField(fieldset, FIELD_DAY, system.getDay(fields));
        fieldset = setField(fieldset, FIELD_HOUR, system.getHour(fields));
        fieldset = setField(fieldset, FIELD_MINUTE, system.getMinute(fields));
        list = new Component[] {
            this.new TextNumericComponent(FIELD_MONTH, 1, new String[] {
                "Январь\u2195", "Февраль\u2195", "Март\u2195", "Апрель\u2195", "Май\u2195", "Июнь\u2195",
                "Июль\u2195", "Август\u2195", "Сентябрь\u2195", "Октябрь\u2195", "Ноябрь\u2195", "Декабрь\u2195"
            }),
            this.new StringComponent(" ", FONT),
            this.new NumericComponent(FIELD_YEAR, 0x0001, 0xffff),
            this.new DaySelectComponent(),
            this.new NumericComponent(FIELD_HOUR, 0, 23),
            this.new StringComponent(" : ", FONT),
            this.new NumericComponent(FIELD_MINUTE, 0, 59),
            this.new StringComponent(" ", FONT),
            this.new TimeZoneComponent()
        };
        (timeZoneList = new StringList(null, null, OFFSET_NAMES)).setCommandListener(new CommandListener() {
            public void commandAction(Command command, Displayable screen) {
                Display parent;
                if(command == DateField.SELECT) (DateField.this).setOffsetIndex(((StringList) screen).getSelectedIndex());
                if((parent = screen.getParentDisplay()) != null) parent.hideSurfaceScreen();
            }
        });
        timeZoneList.addCommand(SELECT);
        timeZoneList.addCommand(BACK);
        this.needPlaceComponents = true;
        this.mode = mode;
        this.focused = -1;
        this.pressed = -1;
        this.ofsindex = index;
        this.fieldset = fieldset;
        this.components = list;
        this.timeZoneList = timeZoneList;
        this.monitor = list;
    }

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

    public void setDate(Date date) {
        boolean needed;
        long time;
        if(date == null) return;
        needed = false;
        time = date.getTime();
        synchronized(monitor)
        {
            long fields;
            long fieldset;
            CalendarSystem system;
            fields = (system = CalendarSystem.gregorian).computeFields(time, getOffset(ofsindex));
            fieldset = setField(0L, FIELD_YEAR, system.getYear(fields));
            fieldset = setField(fieldset, FIELD_MONTH, system.getMonth(fields));
            fieldset = setField(fieldset, FIELD_DAY, system.getDay(fields));
            fieldset = setField(fieldset, FIELD_HOUR, system.getHour(fields));
            fieldset = setField(fieldset, FIELD_MINUTE, system.getMinute(fields));
            if(this.fieldset != (this.fieldset = fieldset)) needed = true;
        }
        if(needed) requestPaint();
    }

    public int getInputMode() {
        return mode;
    }

    public Date getDate() {
        int year;
        int month;
        int day;
        int hour;
        int minute;
        int index = this.ofsindex;
        long fieldset = this.fieldset;
        year = getField(fieldset, FIELD_YEAR);
        month = getField(fieldset, FIELD_MONTH);
        day = getField(fieldset, FIELD_DAY);
        hour = getField(fieldset, FIELD_HOUR);
        minute = getField(fieldset, FIELD_MINUTE);
        return new Date(CalendarSystem.gregorian.computeTime(year, month, day, hour, minute, 0, 0, getOffset(index)));
    }

    void paint(Graphics render, int contentWidth, int contentHeight) {
        int l = render.getClipX();
        int t = render.getClipY();
        int w = render.getClipWidth();
        int h = render.getClipHeight();
        int c = placeComponents();
        int p = pressed;
        long fieldset = this.fieldset;
        Component[] list = components;
        String ofs = OFFSET_NAMES[ofsindex];
        if(!super.focused)
        {
            c = -1;
            p = -1;
        }
        for(int i = list.length; i-- > 0; )
        {
            int cl;
            int ct;
            int cw;
            int ch;
            Component cmp;
            if((cmp = list[i]).visible && (cl = cmp.left) < l + w && (ct = cmp.top) < t + h && cl + (cw = cmp.width) > l && ct + (ch = cmp.height) > t)
            {
                render.translate(cl - render.getTranslateX(), ct - render.getTranslateY());
                cmp.paint(render, cw, ch, i == c, i == p, getField(fieldset, cmp.field), ofs);
            }
        }
    }

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

    void onKeyboardEvent(KeyboardEvent event) {
        components[current].keyboardEvent(event);
    }

    void onPointerEvent(PointerEvent event) {
        int f;
        int x;
        int y;
        Component[] list = components;
        switch(event.getAction())
        {
        case PointerEvent.ACTION_POINTER_PRESSED:
            x = event.getX();
            y = event.getY();
            for(int len = list.length, i = 0; i < len; i++)
            {
                int cl;
                int ct;
                Component cmp;
                if((cmp = list[i]).visible && x >= (cl = cmp.left) && x < cl + cmp.width && y >= (ct = cmp.top) && y < ct + cmp.height && cmp.focusMove(DIR_NONE, current == i))
                {
                    setCurrent(i);
                    focused = pressed = i;
                    event.translate(cl, ct);
                    cmp.pointerEvent(event);
                    requestPaint();
                    break;
                }
            }
            break;
        case PointerEvent.ACTION_POINTER_RELEASED:
            if((f = focused) >= 0)
            {
                Component cmp = list[f];
                focused = pressed = -1;
                event.translate(cmp.left, cmp.top);
                cmp.pointerEvent(event);
                requestPaint();
            }
            break;
        default:
            if((f = focused) >= 0)
            {
                int cl;
                int ct;
                Component cmp = list[f];
                x = event.getX();
                y = event.getY();
                event.translate(cl = cmp.left, ct = cmp.top);
                cmp.pointerEvent(event);
                if(pressed != (pressed = x >= cl && x < cl + cmp.width && y >= ct && y < ct + cmp.height ? f : -1)) requestPaint();
            }
            break;
        }
    }

    void onFocusLost() {
        focused = -1;
        pressed = -1;
        super.onFocusLost();
    }

    boolean onFocusMove(int direction, int viewportWidth, int viewportHeight, int[] visibleRectangle) {
        int c;
        Component[] list = components;
        Component cmp;
        if(!super.onFocusMove(direction, viewportWidth, viewportHeight, visibleRectangle))
        {
            cmp = null;
            switch(direction)
            {
            default:
                for(int len = list.length, i = 0; i < len; i++) if((cmp = list[i]).visible && cmp.focusMove(direction, false))
                {
                    setCurrent(i);
                    break;
                }
                break;
            case DIR_UP:
            case DIR_LEFT:
                for(int i = list.length; i-- > 0; ) if((cmp = list[i]).visible && cmp.focusMove(direction, false))
                {
                    setCurrent(i);
                    break;
                }
                break;
            }
            cmp.writeRectangle(visibleRectangle);
            return true;
        }
        if((cmp = list[c = current]).focusMove(direction, true))
        {
            cmp.writeRectangle(visibleRectangle);
            return true;
        }
        switch(direction)
        {
        case DIR_RIGHT:
        case DIR_DOWN:
            for(int len = list.length, i = c + 1; i < len; i++) if((cmp = list[i]).visible && cmp.focusMove(direction, false))
            {
                setCurrent(i);
                cmp.writeRectangle(visibleRectangle);
                return true;
            }
            break;
        case DIR_LEFT:
        case DIR_UP:
            for(int i = c; i-- > 0; ) if((cmp = list[i]).visible && cmp.focusMove(direction, false))
            {
                setCurrent(i);
                cmp.writeRectangle(visibleRectangle);
                return true;
            }
            break;
        default:
            cmp.writeRectangle(visibleRectangle);
            return true;
        }
        return false;
    }

    int getMinimumContentWidth() {
        placeComponents();
        return width;
    }

    int getMinimumContentHeight() {
        placeComponents();
        return height;
    }

    final void showTimeZoneList() {
        Display parent;
        Displayable owner;
        if((owner = this.owner) != null && (parent = owner.getParentDisplay()) != null)
        {
            StringList list;
            (list = timeZoneList).setSelectedIndex(ofsindex);
            parent.setCurrent(list);
        }
    }

    final void setOffsetIndex(int offsetIndex) {
        boolean needed;
        synchronized(monitor)
        {
            needed = this.ofsindex != (this.ofsindex = offsetIndex);
        }
        if(needed)
        {
            super.notifyStateChanged();
            super.requestPaint();
        }
    }

    final void setFieldValue(int field, int value) {
        boolean needed;
        synchronized(monitor)
        {
            long fieldsetOld;
            long fieldsetNew = fieldsetOld = fieldset;
            if(field == FIELD_YEAR)
            {
                int numberOfDays = CalendarSystem.gregorian.computeNumberOfDays(value, getField(fieldsetNew, FIELD_MONTH));
                if(getField(fieldsetNew, FIELD_DAY) > numberOfDays) fieldsetNew = setField(fieldsetNew, FIELD_DAY, numberOfDays);
            }
            else if(field == FIELD_MONTH)
            {
                int numberOfDays = CalendarSystem.gregorian.computeNumberOfDays(getField(fieldsetNew, FIELD_YEAR), value);
                if(getField(fieldsetNew, FIELD_DAY) > numberOfDays) fieldsetNew = setField(fieldsetNew, FIELD_DAY, numberOfDays);
            }
            fieldsetNew = setField(fieldsetNew, field, value);
            needed = fieldsetOld != (fieldset = fieldsetNew);
        }
        if(needed)
        {
            super.notifyStateChanged();
            super.requestPaint();
        }
    }

    final int getOffsetIndex() {
        return ofsindex;
    }

    final int getFieldValue(int field) {
        return getField(fieldset, field);
    }

    private void setCurrent(int current) {
        this.current = current;
        super.removeCommand(SELECT);
        if(components[current].hasSelectCommand()) super.setDefaultCommand(SELECT);
    }

    private int placeComponents() {
        boolean date;
        boolean time;
        boolean needed;
        int mode;
        int result;
        int row1Width;
        int row2Width;
        int row3Width;
        int itemWidth;
        int row1Height;
        int row2Height;
        int row3Height;
        Component[] list;
        synchronized(monitor)
        {
            if(needed = needPlaceComponents) needPlaceComponents = false;
        }
        if(!needed) return current;
        mode = this.mode;
        for(int i = (list = components).length; i-- > 0; )
        {
            Component cmp;
            if((cmp = list[i]).visible = (mode & (i < 4 ? DATE : TIME)) != 0)
            {
                cmp.width = cmp.getWidth();
                cmp.height = cmp.getHeight();
            }
        }
        if(date = (mode & DATE) != 0)
        {
            Component cmp = list[3];
            row1Width = getWidth(list, 0, 3);
            row1Height = getHeight(list, 0, 3);
            row2Width = cmp.width;
            row2Height = cmp.height;
        } else
        {
            row1Width = row1Height = row2Width = row2Height = 0;
        }
        if(time = (mode & TIME) != 0)
        {
            row3Width = getWidth(list, 4, 5);
            row3Height = getHeight(list, 4, 5);
        } else
        {
            row3Width = row3Height = 0;
        }
        width = itemWidth = Math.max(Math.max(row1Width, row2Width), row3Width);
        height = row1Height + row2Height + row3Height;
        if(date)
        {
            setLeft(list, 0, 3, (itemWidth - row1Width) >> 1);
            setLeft(list, 3, 1, (itemWidth - row2Width) >> 1);
            setTop(list, 0, 3, row1Height, 0);
            setTop(list, 3, 1, row2Height, row1Height);
        }
        if(time)
        {
            setLeft(list, 4, 5, (itemWidth - row3Width) >> 1);
            setTop(list, 4, 5, row3Height, row1Height + row2Height);
        }
        if(!list[result = current].visible) setCurrent(result = date ? 0 : 4);
        return result;
    }
}
