/*
    Реализация спецификаций 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.io.*;
import javax.microedition.midlet.*;
import javax.microedition.rms.*;
import malik.emulator.application.*;
import malik.emulator.media.graphics.*;
import malik.emulator.microedition.*;
import malik.emulator.util.*;

public class Display extends VisibleElement implements Rectangle
{
    static abstract class OneShotAction extends Object implements Runnable
    {
        private static final int FREED     = 0;
        private static final int REQUESTED = 1;
        private static final int EXECUTING = 2;
        private static final int REPEATED  = 3;

        private int state;
        private Object service;
        protected final Object monitor;

        protected OneShotAction() {
            this.monitor = new Object();
        }

        protected OneShotAction(Object monitor) {
            this.monitor = monitor == null ? new Object() : monitor;
        }

        public final void run() {
            boolean exec = false;
            Object monitor;
            synchronized(monitor = this.monitor)
            {
                if(state == REQUESTED)
                {
                    state = EXECUTING;
                    exec = true;
                }
            }
            if(exec) serviceExecute(monitor);
        }

        public final void service() {
            boolean exec = false;
            Object monitor;
            synchronized(monitor = this.monitor)
            {
                if(Thread.currentThread() == service)
                {
                    if(state == REQUESTED)
                    {
                        state = EXECUTING;
                        exec = true;
                    }
                } else
                {
                    while(state != FREED)
                    {
                        try
                        {
                            monitor.wait();
                        }
                        catch(InterruptedException e)
                        {
                            e.printRealStackTrace();
                        }
                    }
                }
            }
            if(exec) serviceExecute(monitor);
        }

        public final void request(Display parent) {
            boolean exec;
            if(parent == null)
            {
                throw new NullPointerException("OneShotAction.request: аргумент parent равен нулевой ссылке.");
            }
            exec = false;
            synchronized(monitor)
            {
                label0:
                {
                    switch(state)
                    {
                    default:
                        break label0;
                    case FREED:
                        state = REQUESTED;
                        break;
                    case EXECUTING:
                        state = REPEATED;
                        break;
                    }
                    service = parent.serviceThread;
                    exec = true;
                }
            }
            if(exec) parent.callSerially(this);
        }

        protected abstract void execute();

        private void serviceExecute(Object monitor) {
            try
            {
                execute();
            }
            catch(RuntimeException e)
            {
                e.printRealStackTrace();
            }
            synchronized(monitor)
            {
                state &= 1;
                monitor.notifyAll();
            }
        }
    }

    private static final class ApplicationTermination extends Object implements Runnable
    {
        private final Requestable stream;
        private final MIDletDispatcher dispatcher;

        public ApplicationTermination(Requestable stream, MIDletDispatcher dispatcher) {
            this.stream = stream;
            this.dispatcher = dispatcher;
        }

        public void run() {
            dispatcher.stop();
            stream.terminate();
        }
    }

    public static final int LIST_ELEMENT = 1;
    public static final int CHOICE_GROUP_ELEMENT = 2;
    public static final int ALERT = 3;
    public static final int COLOR_BACKGROUND = 0;
    public static final int COLOR_FOREGROUND = 1;
    public static final int COLOR_HIGHLIGHTED_BACKGROUND = 2;
    public static final int COLOR_HIGHLIGHTED_FOREGROUND = 3;
    public static final int COLOR_BORDER = 4;
    public static final int COLOR_HIGHLIGHTED_BORDER = 5;

    private static long lastUpdated;
    private static ApplicationThread[] appThreads;
    private static final Clip screenClip;
    private static final Object screenMonitor;

    static {
        appThreads = new ApplicationThread[7];
        screenClip = new Clip(RasterCanvas.MAX_SCREEN_WIDTH, RasterCanvas.MAX_SCREEN_HEIGHT);
        screenMonitor = new Object();
    }

    public static void registerApplication(MIDlet application) {
        Thread current;
        if(!((current = Thread.currentThread()) instanceof ApplicationThread) || !((ApplicationThread) current).setApplication(application))
        {
            throw new SecurityException("MIDlet: невозможно создать новый мидлет.");
        }
    }

    public static Display getDisplay(MIDlet application) {
        ApplicationThread[] appthreads;
        if(application == null)
        {
            throw new NullPointerException("Display.getDisplay: аргумент application равен нулевой ссылке.");
        }
        for(int i = (appthreads = appThreads).length; i-- > 0; )
        {
            ApplicationThread current;
            if((current = appthreads[i]) != null && current.getApplication() == application) return current.parentDisplay();
        }
        return DeviceManager.getInstance().getMainDisplay();
    }

    private static void addAppThread(ApplicationThread appthread) {
        synchronized(screenMonitor)
        {
            int len;
            ApplicationThread[] appthreads;
            if(Array.findf(appthreads = appThreads, 0, appthread) >= (len = appthreads.length))
            {
                int index;
                if((index = Array.findf(appthreads, 0, null)) >= len)
                {
                    Array.copy(appthreads, 0, appthreads = new ApplicationThread[(len << 1) + 1], 0, len);
                    appThreads = appthreads;
                }
                appthreads[index] = appthread;
            }
        }
    }

    private static void removeAppThread(Thread appthread) {
        synchronized(screenMonitor)
        {
            int index;
            ApplicationThread[] appthreads;
            if((index = Array.findb(appthreads = appThreads, appthreads.length - 1, appthread)) >= 0) appthreads[index] = null;
        }
    }

    private class ActionStream extends Requestable implements Runnable
    {
        private final class DelayedAction extends Scheduler.Task
        {
            private final Runnable action;

            public DelayedAction(Runnable action) {
                this.action = action;
            }

            public void run() {
                ((Requestable) ActionStream.this).request(action, 0L);
            }
        }

        private final int capacity;
        private final RequestableQueue queue;

        public ActionStream(int initialCapacity, int historyCapacity) {
            this.capacity = historyCapacity;
            this.queue = new RequestableQueue(initialCapacity);
        }

        public synchronized void request(int action, int param1, int param2, int param3) {
            queue.addTailElement(null, (long) (action & 0xffff) | (long) (param1 & 0xffff) << 0x10 | (long) (param2 & 0xffff) << 0x20 | (long) (param3 & 0xffff) << 0x30);
            notify();
        }

        public void request(Runnable action, long delay) {
            if(delay > 0L)
            {
                if(action != null) Scheduler.schedule(this.new DelayedAction(action), delay, Scheduler.MONOPOLY);
                return;
            }
            synchronized(this)
            {
                queue.addTailElement(action, -1L);
                notify();
            }
        }

        public synchronized void terminate() {
            super.terminate();
            notify();
        }

        public void run() {
            int historyCapacity = capacity;
            int[] ids = InputDevice.getIDs();
            RequestableQueue queue = this.queue;
            KeyboardEvent kbdevent = new KeyboardEvent(historyCapacity);
            PointerEvent ptrevent = new PointerEvent(historyCapacity);
            InputDevice keyboard = null;
            InputDevice pointing = null;
            InputDevice device;
            for(int len = ids.length, i = 0; i < len; i++) if(!(device = InputDevice.get(ids[i])).isVirtual())
            {
                if(device.sourceSupported(InputDevice.SOURCE_CLASS_BUTTON) && device.getKeyboardType() != InputDevice.KEYBOARD_TYPE_NONE)
                {
                    keyboard = device;
                }
                else if(device.sourceSupported(InputDevice.SOURCE_CLASS_POINTER))
                {
                    pointing = device;
                }
            }
            historyCapacity = 0;
            ids = null;
            device = null;
            for(Display parent = Display.this; ; )
            {
                long fromUser;
                Runnable fromApp;
                Displayable background;
                Displayable foreground;
                waitAction();
                if(super.terminated()) break;
                synchronized(this)
                {
                    if(queue.isEmpty())
                    {
                        fromUser = -1L;
                        fromApp = null;
                    } else
                    {
                        fromUser = queue.peekHeadLong();
                        fromApp = queue.peekHeadRunnable();
                        ((Queue) queue).removeHeadElement();
                    }
                }
                if(fromApp != null)
                {
                    try
                    {
                        fromApp.run();
                    }
                    catch(RuntimeException e)
                    {
                        e.printRealStackTrace();
                    }
                    continue;
                }
                if(fromUser != -1L && (foreground = parent.getActiveForeground()) != null)
                {
                    int action = (short) fromUser;
                    int param1 = (short) (fromUser >> 0x10);
                    int param2 = (short) (fromUser >> 0x20);
                    int param3 = (short) (fromUser >> 0x30);
                    switch(action)
                    {
                    case ACTION_WINDOW_SHOW:
                        if((background = parent.getActiveBackground()) != null && background != foreground) background.eventShow(parent);
                        foreground.eventShow(parent);
                        foreground.requestPaintAll();
                        break;
                    case ACTION_WINDOW_HIDE:
                        foreground.eventHide();
                        if((background = parent.getActiveBackground()) != null && background != foreground) background.eventHide();
                        break;
                    case ACTION_KEY_REPEATED:
                    case ACTION_KEY_PRESSED:
                        kbdevent.addStory(action, param1, param2 & 0xffff | param3 << 0x10, keyboard, InputDevice.SOURCE_KEYBOARD, 0L);
                        foreground.eventKeyboard(kbdevent);
                        break;
                    case ACTION_KEY_RELEASED:
                        kbdevent.addStory(action, param1, 0, keyboard, InputDevice.SOURCE_KEYBOARD, 0L);
                        foreground.eventKeyboard(kbdevent);
                        break;
                    case ACTION_POINTER_DRAGGED:
                        ptrevent.addStory(action, 0, param2, param3, pointing, InputDevice.SOURCE_MOUSE, 0L);
                        ptrevent.translateReset();
                        foreground.eventPointer(ptrevent);
                        break;
                    case ACTION_POINTER_PRESSED:
                    case ACTION_POINTER_RELEASED:
                    case ACTION_BUTTON_PRESSED:
                    case ACTION_BUTTON_RELEASED:
                        ptrevent.addStory(action, param1, param2, param3, pointing, InputDevice.SOURCE_MOUSE, 0L);
                        ptrevent.translateReset();
                        foreground.eventPointer(ptrevent);
                        break;
                    }
                }
            }
        }

        private synchronized void waitAction() {
            for(; ; )
            {
                try
                {
                    wait();
                    break;
                }
                catch(InterruptedException e)
                {
                    e.printRealStackTrace();
                }
            }
        }
    }

    private final class ApplicationThread extends Thread
    {
        private MIDlet application;

        public ApplicationThread(ActionStream target) {
            super(target, "Поток исполнения, обслуживающий события мидлета");
        }

        public boolean setApplication(MIDlet application) {
            if(this.application == null)
            {
                this.application = application;
                return true;
            }
            return false;
        }

        public MIDlet getApplication() {
            return application;
        }

        public Display parentDisplay() {
            return Display.this;
        }
    }

    private final class ApplicationExecution extends ActionStream
    {
        private final String typeName;
        private final MIDletDispatcher dispatcher;

        public ApplicationExecution(int initialCapacity, int historyCapacity, String typeName, MIDletDispatcher dispatcher) {
            super(initialCapacity, historyCapacity);
            this.typeName = typeName;
            this.dispatcher = dispatcher;
        }

        public void run() {
            int status;
            Display parent = Display.this;
            PrintStream printer = System.out;
            String typeName = this.typeName;
            MIDletDispatcher dispatcher = this.dispatcher;

            /* вывод сообщений о запуске мидлета */
            printer.println("Запуск приложения… Класс мидлета: ".concat(typeName));

            /* запуск мидлета */
            status = dispatcher.start();
            parent.notifyStarted();

            if(status != MIDletDispatcher.OK)
            {
                /* вывод сообщений о неудачной попытке запуска */
                printer.println("Не удалось запустить – приложение завершает работу. Класс мидлета: ".concat(typeName));

                /* остановка мидлета */
                if(status == MIDletDispatcher.MUST_STOPPED) dispatcher.stop();
                parent.notifyStopped();
                return;
            }

            /* вывод сообщений об удачной попытке запуска */
            printer.println("Приложение запущено и работает. Класс мидлета: ".concat(typeName));

            /* обработка событий мидлета */
            super.run();

            /* вывод сообщений о завершении мидлета */
            printer.println("Приложение завершило работу. Класс мидлета: ".concat(typeName));

            /* остановка мидлета */
            RecordStore.closeAllRecordStores();
            parent.notifyStopped();
        }
    }

    private int left;
    private int top;
    private final int width;
    private final int height;
    private final RasterBuffer screenBuffer;
    private final ScreenGraphics screenGraphics;
    private MIDletDispatcher serviceDispatcher;
    private Requestable serviceStream;
    Thread serviceThread;
    private final Runnable serviceStarted;
    private final Runnable serviceStopped;
    Displayable currentBackground;
    SurfaceScreen currentForeground;
    Displayable activeBackground;
    SurfaceScreen activeForeground;
    private final OneShotAction activeChanged;
    private final Object monitor;

    Display(boolean system, boolean visibility, int left, int top, int width, int height) {
        super(visibility);
        RasterBuffer buffer;
        if(width < RasterCanvas.MIN_SCREEN_WIDTH || width > RasterCanvas.MAX_SCREEN_WIDTH)
        {
            throw new IllegalArgumentException("Display: аргумент width выходит из диапазона.");
        }
        if(height < RasterCanvas.MIN_SCREEN_HEIGHT || height > RasterCanvas.MAX_SCREEN_HEIGHT)
        {
            throw new IllegalArgumentException("Display: аргумент height выходит из диапазона.");
        }
        this.left = left;
        this.top = top;
        this.width = width;
        this.height = height;
        this.screenBuffer = buffer = RasterBuffer.create(width, height, 0xffffff, true);
        this.screenGraphics = new ScreenGraphics(buffer);
        this.serviceStream = system ? Run.instance : null;
        this.serviceThread = system ? Thread.currentThread() : null;
        this.serviceStarted = new Runnable() {
            public void run() {
                DeviceManager.getInstance().applicationStarted(Display.this);
            }
        };
        this.serviceStopped = new Runnable() {
            public void run() {
                DeviceManager.getInstance().applicationStopped(Display.this);
            }
        };
        this.activeChanged = new OneShotAction() {
            public void execute() {
                Display parent = Display.this;
                Displayable oldForeground = parent.activeForeground;
                Displayable oldBackground = parent.activeBackground;
                Displayable newBackground = parent.currentBackground;
                SurfaceScreen newForeground = parent.currentForeground;
                parent.activeBackground = newBackground;
                parent.activeForeground = newForeground;
                if(!parent.visibility) return;
                if(oldBackground == newBackground && oldForeground != newForeground)
                {
                    if(oldForeground != null) oldForeground.eventHide();
                    if(newForeground != null)
                    {
                        newForeground.eventShow(parent);
                        newForeground.requestPaintAll();
                        return;
                    }
                    if(oldBackground != null) oldBackground.requestPaintAll();
                    return;
                }
                if(oldBackground != newBackground && oldForeground == newForeground)
                {
                    if(oldForeground != null)
                    {
                        if(oldBackground != null) oldBackground.eventHide();
                        if(newBackground != null) newBackground.eventShow(parent);
                        oldForeground.requestPaintAll();
                        return;
                    }
                    if(oldBackground != null) oldBackground.eventHide();
                    if(newBackground != null)
                    {
                        newBackground.eventShow(parent);
                        newBackground.requestPaintAll();
                    }
                    return;
                }
                if(oldBackground != newBackground && oldForeground != newForeground)
                {
                    if(oldForeground != null) oldForeground.eventHide();
                    if(oldBackground != null) oldBackground.eventHide();
                    if(newBackground != null) newBackground.eventShow(parent);
                    if(newForeground != null)
                    {
                        newForeground.eventShow(parent);
                        newForeground.requestPaintAll();
                        return;
                    }
                    if(newBackground != null) newBackground.requestPaintAll();
                }
            }
        };
        this.monitor = new Object();
    }

    public void handleKeyboardEvent(KeyboardEvent event) {
        Requestable stream;
        if(event == null)
        {
            throw new NullPointerException("Display.handleKeyboardEvent: аргумент event равен нулевой ссылке.");
        }
        if(Thread.currentThread() == serviceThread)
        {
            Displayable foreground;
            if(visibility && (foreground = getActiveForeground()) != null) foreground.eventKeyboard(event);
            return;
        }
        if((stream = serviceStream) != null)
        {
            int action;
            switch(action = event.getAction())
            {
            case KeyboardEvent.ACTION_KEY_REPEATED:
            case KeyboardEvent.ACTION_KEY_PRESSED:
                if(visibility) stream.requestKeyboardEvent(action, event.getKey(), event.getCharCode());
                break;
            case KeyboardEvent.ACTION_KEY_RELEASED:
                if(visibility) stream.requestKeyboardEvent(action, event.getKey(), 0);
                break;
            }
        }
    }

    public void handlePointerEvent(PointerEvent event) {
        Requestable stream;
        if(event == null)
        {
            throw new NullPointerException("Display.handlePointerEvent: аргумент event равен нулевой ссылке.");
        }
        if(Thread.currentThread() == serviceThread)
        {
            Displayable foreground;
            if(visibility && (foreground = getActiveForeground()) != null) foreground.eventPointer(event);
            return;
        }
        if((stream = serviceStream) != null)
        {
            int action;
            switch(action = event.getAction())
            {
            case PointerEvent.ACTION_POINTER_DRAGGED:
                if(visibility) stream.requestPointerEvent(action, 0, event.getX(), event.getY());
                break;
            case PointerEvent.ACTION_POINTER_PRESSED:
            case PointerEvent.ACTION_POINTER_RELEASED:
            case PointerEvent.ACTION_BUTTON_PRESSED:
            case PointerEvent.ACTION_BUTTON_RELEASED:
                if(visibility) stream.requestPointerEvent(action, event.getButton(), event.getX(), event.getY());
                break;
            }
        }
    }

    public void callSerially(Runnable action) {
        Requestable stream;
        if(action != null && (stream = serviceStream) != null) stream.request(action, 0L);
    }

    public void callSerially(Runnable action, long delay) {
        Requestable stream;
        if(action != null && (stream = serviceStream) != null) stream.request(action, delay);
    }

    public void setCurrent(Displayable screen) {
        if(screen == null) return;
        if(screen instanceof SurfaceScreen)
        {
            currentForeground = (SurfaceScreen) screen;
        } else
        {
            currentBackground = screen;
            currentForeground = null;
        }
        activeChanged.request(this);
    }

    public void setCurrent(Alert foreground, Displayable background) {
        if(foreground == null)
        {
            throw new NullPointerException("Display.setCurrent: аргумент foreground равен нулевой ссылке.");
        }
        if(background == null)
        {
            throw new NullPointerException("Display.setCurrent: аргумент background равен нулевой ссылке.");
        }
        if(background instanceof SurfaceScreen)
        {
            throw new IllegalArgumentException("Display.setCurrent: аргумент background не может иметь тип SurfaceScreen.");
        }
        currentBackground = background;
        currentForeground = foreground;
        activeChanged.request(this);
    }

    public void setCurrent(SurfaceScreen foreground, Displayable background) {
        if(foreground == null)
        {
            throw new NullPointerException("Display.setCurrent: аргумент foreground равен нулевой ссылке.");
        }
        if(background == null)
        {
            throw new NullPointerException("Display.setCurrent: аргумент background равен нулевой ссылке.");
        }
        if(background instanceof SurfaceScreen)
        {
            throw new IllegalArgumentException("Display.setCurrent: аргумент background не может иметь тип SurfaceScreen.");
        }
        currentBackground = background;
        currentForeground = foreground;
        activeChanged.request(this);
    }

    public void setCurrentItem(Item item) {
        Screen screen;
        if(item == null)
        {
            throw new NullPointerException("Display.setCurrentItem: аргумент item равен нулевой ссылке.");
        }
        if((screen = item.owner) == null)
        {
            throw new IllegalStateException("Display.setCurrentItem: элемент не принадлежит какому-либо экрану.");
        }
        currentBackground = screen;
        currentForeground = null;
        activeChanged.request(this);
        screen.setFocus(item, this);
    }

    public boolean flashBacklight(int duration) {
        if(duration < 0)
        {
            throw new IllegalArgumentException("Display.flashBacklight: аргумент duration не может быть отрицательным.");
        }
        if(visibility)
        {
            DeviceManager.getInstance().getBacklight().start((long) duration);
            return true;
        }
        return false;
    }

    public boolean vibrate(int duration) {
        if(duration < 0)
        {
            throw new IllegalArgumentException("Display.vibrate: аргумент duration не может быть отрицательным.");
        }
        if(visibility)
        {
            DeviceManager.getInstance().getVibrator().start((long) duration);
            return true;
        }
        return false;
    }

    public boolean isColor() {
        return true;
    }

    public int numColors() {
        return 0x01000000;
    }

    public int numAlphaLevels() {
        return 0x00000100;
    }

    public int getBestImageWidth(int type) {
        switch(type)
        {
        case CHOICE_GROUP_ELEMENT:
            return ChoiceGroup.ICON_WIDTH;
        case LIST_ELEMENT:
            return List.ICON_WIDTH;
        case ALERT:
            return Alert.ICON_WIDTH;
        default:
            throw new IllegalArgumentException("Display.getBestImageWidth: аргумент type имеет недопустимое значение.");
        }
    }

    public int getBestImageHeight(int type) {
        switch(type)
        {
        case CHOICE_GROUP_ELEMENT:
            return ChoiceGroup.ICON_HEIGHT;
        case LIST_ELEMENT:
            return List.ICON_HEIGHT;
        case ALERT:
            return Alert.ICON_HEIGHT;
        default:
            throw new IllegalArgumentException("Display.getBestImageHeight: аргумент type имеет недопустимое значение.");
        }
    }

    public int getBorderStyle(boolean highlighted) {
        return Graphics.SOLID;
    }

    public int getColor(int colorSpecifier) {
        switch(colorSpecifier)
        {
        case COLOR_FOREGROUND:
            return RasterCanvas.getSystemColor(0x20);
        case COLOR_HIGHLIGHTED_BACKGROUND:
            return RasterCanvas.getSystemColor(0x19);
        case COLOR_HIGHLIGHTED_FOREGROUND:
            return RasterCanvas.getSystemColor(0x21);
        case COLOR_BACKGROUND:
        case COLOR_BORDER:
        case COLOR_HIGHLIGHTED_BORDER:
            return RasterCanvas.getSystemColor(0x05);
        default:
            throw new IllegalArgumentException("Display.getColor: аргумент colorSpecifier имеет недопустимое значение.");
        }
    }

    public Displayable getCurrent() {
        SurfaceScreen result;
        return (result = currentForeground) != null && (result instanceof Alert) ? result : currentBackground;
    }

    public Displayable getBackground() {
        return currentBackground;
    }

    public Displayable getForeground() {
        Displayable result;
        return (result = currentForeground) != null ? result : currentBackground;
    }

    public final void show() {
        if(!visibility) showDisplay();
    }

    public final void hide() {
        if(visibility) hideDisplay();
    }

    public final void setVisibility(boolean visibility) {
        boolean current = this.visibility;
        if(visibility && !current)
        {
            showDisplay();
            return;
        }
        if(!visibility && current) hideDisplay();
    }

    public final boolean isVisible() {
        return visibility;
    }

    public final int getLeft() {
        return left;
    }

    public final int getTop() {
        return top;
    }

    public final int getWidth() {
        return width;
    }

    public final int getHeight() {
        return height;
    }

    public final void setPosition(int left, int top) {
        boolean needRepaint = false;
        Displayable foreground;
        synchronized(screenClip)
        {
            if(this.left != left || this.top != top)
            {
                needRepaint = true;
                this.left = left;
                this.top = top;
            }
        }
        if(needRepaint && visibility && (foreground = getActiveForeground()) != null) foreground.requestPaintAll();
   }

    public final Displayable getActiveBackground() {
        return activeBackground;
    }

    public final Displayable getActiveForeground() {
        Displayable result;
        return (result = activeForeground) != null ? result : activeBackground;
    }

    final void update() {
        int behavior;
        long period;
        long milliseconds;
        RasterCanvas screen;
        if(!visibility) return;
        behavior = 0;
        milliseconds = 0L;
        period = (long) DeviceManager.getInstance().getSettings().getMinimumPeriod();
        synchronized(screenMonitor)
        {
            long last = lastUpdated;
            long curr = System.currentTimeMillis();
            long prev = last - period;
            long next = last + period;
            if(curr >= prev && curr < last)
            {
                milliseconds = last - curr;
                behavior = 1;
            }
            else if(curr >= last && curr < next)
            {
                lastUpdated = next;
                milliseconds = next - curr;
                behavior = 2;
            }
            else
            {
                lastUpdated = curr;
                behavior = 3;
            }
        }
        try
        {
            switch(behavior)
            {
            case 1:
                Thread.sleep(milliseconds);
                break;
            case 2:
                Thread.sleep(milliseconds);
                /* fall through */
            case 3:
                if(visibility) synchronized(screenClip)
                {
                    if(visibility)
                    {
                        (screen = RasterCanvas.screen).drawPixels(screenBuffer, RasterCanvas.TRANSFORM_NONE, this, screenClip);
                        screen.updateScreen();
                    }
                }
                break;
            }
        }
        catch(InterruptedException e)
        {
            e.printRealStackTrace();
        }
    }

    final void notifyStarted() {
        Run.instance.request(serviceStarted);
    }

    final void notifyStopped() {
        removeAppThread(serviceThread);
        serviceDispatcher = null;
        serviceStream = null;
        serviceThread = null;
        currentBackground = null;
        currentForeground = null;
        activeBackground = null;
        activeForeground = null;
        Run.instance.request(serviceStopped);
    }

    final void hideSurfaceScreen() {
        currentForeground = null;
        activeChanged.request(this);
    }

    final int start(String applicationTypeName) {
        int error = 0;
        ApplicationThread appthread = null;
        synchronized(monitor)
        {
            label0:
            {
                Requestable stream;
                ActionStream execution;
                MIDletDispatcher dispatcher;
                if((stream = serviceStream) == Run.instance)
                {
                    error = 1;
                    break label0;
                }
                if(stream != null)
                {
                    error = 2;
                    break label0;
                }
                if(applicationTypeName == null)
                {
                    error = 3;
                    break label0;
                }
                this.serviceDispatcher = dispatcher = new MIDletDispatcher(applicationTypeName);
                this.serviceStream = execution = this.new ApplicationExecution(0x10, 0x10, applicationTypeName, dispatcher);
                this.serviceThread = appthread = this.new ApplicationThread(execution);
            }
        }
        if(error != 0) return error;
        addAppThread(appthread);
        ((Thread) appthread).start();
        return 0;
    }

    final int stop(boolean destroyAppInvoke) {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                Requestable stream;
                if((stream = serviceStream) == Run.instance)
                {
                    error = 1;
                    break label0;
                }
                if(stream == null)
                {
                    error = 2;
                    break label0;
                }
                if(destroyAppInvoke)
                {
                    stream.request(new ApplicationTermination(stream, serviceDispatcher));
                    break label0;
                }
                stream.terminate();
            }
        }
        return error;
    }

    final MIDlet application() {
        Thread thread;
        return (thread = serviceThread) instanceof ApplicationThread ? ((ApplicationThread) thread).getApplication() : null;
    }

    final ScreenGraphics graphics() {
        return screenGraphics;
    }

    private void showDisplay() {
        Displayable background;
        Displayable foreground;
        Requestable stream;
        visibility = true;
        if(Thread.currentThread() == serviceThread)
        {
            if((foreground = getActiveForeground()) != null)
            {
                if((background = getActiveBackground()) != null && background != foreground) background.eventShow(this);
                foreground.eventShow(this);
                foreground.requestPaintAll();
            }
            return;
        }
        update();
        if((stream = serviceStream) != null) stream.requestWindowShow();
    }

    private void hideDisplay() {
        Displayable background;
        Displayable foreground;
        Requestable stream;
        visibility = false;
        if(Thread.currentThread() == serviceThread)
        {
            if((foreground = getActiveForeground()) != null)
            {
                foreground.eventHide();
                if((background = getActiveBackground()) != null && background != foreground) background.eventHide();
            }
            return;
        }
        if((stream = serviceStream) != null) stream.requestWindowHide();
    }
}
