/*
    Реализация спецификаций 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 malik.emulator.microedition;

import java.io.*;
import javax.microedition.io.*;
import javax.microedition.lcdui.*;
import javax.microedition.media.*;
import javax.microedition.media.protocol.*;
import malik.emulator.application.*;
import malik.emulator.fileformats.graphics.*;
import malik.emulator.media.graphics.*;
import malik.emulator.media.sound.*;
import malik.emulator.microedition.media.*;

public abstract class DeviceManager extends AppProxy
{
    public static final int READ       = 1;
    public static final int WRITE      = 2;
    public static final int READ_WRITE = 3;
    public static final String TONE_DEVICE_LOCATOR = "device://tone";
    public static final String MIDI_DEVICE_LOCATOR = "device://midi";

    private static DeviceManager INSTANCE;

    public static DeviceManager getInstance() {
        return INSTANCE;
    }

    private boolean paused;
    private Display current;
    private final Display main;
    private final DeviceSettings settings;
    private final DeviceControl vibrator;
    private final DeviceControl backlight;
    private final TimeBase systemTimeBase;
    private SyntheticSoundPlayer tonePlayer;

    protected DeviceManager() {
        SystemDisplay display;
        DeviceSettings settings;
        DeviceControl empty;
        DeviceControl vibrator;
        DeviceControl backlight;
        TimeBase systemTimeBase;
        if(INSTANCE != null)
        {
            throw new InstantiationError("DeviceManager: нельзя создать ещё один экземпляр DeviceManager. Используйте выражение DeviceManager.getInstance().");
        }
        INSTANCE = this;
        empty = new EmptyDeviceControl();
        if((settings = createSettings()) == null) settings = new DeviceSettings();
        this.settings = settings;
        settings.loadMIDletDescriptor();
        settings.loadMIDletProperties();
        if((display = createMainDisplay()) == null)
        {
            RasterCanvas screen = RasterCanvas.screen;
            display = new SystemDisplay(false, screen.getWidth(), screen.getHeight());
        }
        this.main = display;
        this.current = display;
        if((vibrator = createVibrator()) == null) vibrator = empty;
        this.vibrator = vibrator;
        if((backlight = createBacklight()) == null) backlight = empty;
        this.backlight = backlight;
        if((systemTimeBase = createSystemTimeBase()) == null) systemTimeBase = new SystemTimeBase();
        this.systemTimeBase = systemTimeBase;
    }

    public abstract String[] getSupportedContentTypes(String protocol);

    public abstract String[] getSupportedProtocols(String contentType);

    public abstract Player createPlayer(String locator) throws IOException, MediaException;

    public abstract Player createPlayer(DataSource source) throws IOException, MediaException;

    public abstract Player createPlayer(InputStream stream, String contentType) throws IOException, MediaException;

    public abstract Connection openConnection(String url, int mode, boolean timeouts) throws IOException;

    public abstract ImageDecoder openImageDecoder(InputStream stream) throws IOException;

    public void playTone(int note, int duration, int volume) throws MediaException {
        int noteOnMessage;
        final int noteOffMessage;
        final SyntheticSoundPlayer tonePlayer;
        if(note < 0x00 || note > 0x7f)
        {
            throw new IllegalArgumentException("Manager.playTone: аргумент note выходит из диапазона.");
        }
        if(duration <= 0)
        {
            throw new IllegalArgumentException("manager.playTone: аргумент duration может быть только положительным.");
        }
        if(volume < 0) volume = 0;
        if(volume > 100) volume = 100;
        volume = volume * SoundPlayer.MAX_VOLUME / 100;
        noteOnMessage = (noteOffMessage = 0x900000 | note << 8) | volume;
        try
        {
            SyntheticSoundPlayer player;
            if((player = this.tonePlayer) == null) player = this.tonePlayer = SyntheticSoundPlayer.open();
            (tonePlayer = player).sendMessage(noteOnMessage);
        }
        catch(SoundPlayerException e)
        {
            throw new MediaException(e.getRealMessage());
        }
        Scheduler.schedule(new Scheduler.Task() {
            public void run() {
                try
                {
                    tonePlayer.sendMessage(noteOffMessage);
                }
                catch(SoundPlayerException e)
                {
                    e.printRealStackTrace();
                }
            }
        }, (long) duration, Scheduler.MONOPOLY);
    }

    public void applicationStarted(Display display) {
        if(display != null) setCurrent(display);
    }

    public void applicationStopped(Display display) {
        setCurrent(main);
    }

    public void setCurrentDisplay(Display current) {
        if(current != null) setCurrent(current);
    }

    public final boolean isPaused() {
        return paused;
    }

    public final Display getCurrentDisplay() {
        return current;
    }

    public final Display getMainDisplay() {
        return main;
    }

    public final DeviceSettings getSettings() {
        return settings;
    }

    public final DeviceControl getVibrator() {
        return vibrator;
    }

    public final DeviceControl getBacklight() {
        return backlight;
    }

    public final TimeBase getSystemTimeBase() {
        return systemTimeBase;
    }

    protected abstract void showStartScreen(Display display);

    protected abstract void showConsoleScreen(Display display);

    protected abstract void showExitAppScreen(Display display);

    protected void appLaunch() {
        Display display;
        setCurrent(display = main);
        showStartScreen(display);
    }

    protected void keyboardEvent(KeyboardEvent event) {
        Display current;
        if((current = this.current) instanceof MIDletDisplay)
        {
            boolean console;
            int key = event.getKey();
            Display main = this.main;
            DeviceSettings settings = this.settings;
            switch(event.getAction())
            {
            case KeyboardEvent.ACTION_KEY_PRESSED:
            case KeyboardEvent.ACTION_KEY_REPEATED:
                if(key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_CONSOLE) || key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_EXITAPP)) return;
                break;
            case KeyboardEvent.ACTION_KEY_RELEASED:
                if((console = key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_CONSOLE)) || key == settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_EXITAPP))
                {
                    if(console)
                    {
                        showConsoleScreen(main);
                    } else
                    {
                        showExitAppScreen(main);
                    }
                    setCurrent(main);
                    return;
                }
                break;
            }
        }
        current.handleKeyboardEvent(event);
    }

    protected void pointerEvent(PointerEvent event) {
        Display current = this.current;
        event.translateTo(current.getLeft(), current.getTop());
        current.handlePointerEvent(event);
    }

    protected void windowShow() {
        paused = false;
        ((VisibleElement) current).show();
    }

    protected void windowHide() {
        paused = true;
        ((VisibleElement) current).hide();
    }

    protected SystemDisplay createMainDisplay() {
        return null;
    }

    protected DeviceSettings createSettings() {
        return null;
    }

    protected DeviceControl createVibrator() {
        return null;
    }

    protected DeviceControl createBacklight() {
        return null;
    }

    protected TimeBase createSystemTimeBase() {
        return null;
    }

    private void setCurrent(Display current) {
        VisibleElement previous = this.current;
        this.current = current;
        previous.hide();
        ((VisibleElement) current).show();
    }
}

final class EmptyDeviceControl extends Object implements DeviceControl
{
    public EmptyDeviceControl() {
    }

    public void start(long durationInMillis) {
    }

    public void start() {
    }

    public void stop() {
    }
}
