{
    EmulatorInterfaces содержит классы и константы, используемые в настройках и
    пользовательском интерфейсе Малик Эмулятора.
    Этот исходный текст является частью Малик Эмулятора.

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

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

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

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

unit EmulatorInterfaces;

{$MODE DELPHI}

interface

uses
    Classes,
    LCLIntf,
    InterfaceBase,
    Graphics,
    Forms,
    Controls,
    Dialogs,
    Menus,
    LMessages,
    Lang,
    EmulMalik;

{%region public }
const
    { коды типов данных }
    DATA_TYPE_BOOLEAN = int(1);
    DATA_TYPE_BYTE = int(2);
    DATA_TYPE_SHORT = int(3);
    DATA_TYPE_INT = int(4);
    DATA_TYPE_LONG = int(5);
    DATA_TYPE_WCHAR = int(6);
    DATA_TYPE_ICHAR = int(7);
    DATA_TYPE_FLOAT = int(8);
    DATA_TYPE_DOUBLE = int(9);
    DATA_TYPE_REAL = int(10);
    DATA_TYPE_OBJECT = int(11);

const
    { сигнатуры типов данных }
    SIGNATURE_VOID = 'V';
    SIGNATURE_BOOLEAN = 'Z';
    SIGNATURE_BYTE = 'B';
    SIGNATURE_SHORT = 'S';
    SIGNATURE_INT = 'I';
    SIGNATURE_LONG = 'J';
    SIGNATURE_WCHAR = 'C';
    SIGNATURE_ICHAR = 'G';
    SIGNATURE_FLOAT = 'F';
    SIGNATURE_DOUBLE = 'D';
    SIGNATURE_REAL = 'E';
    SIGNATURE_CLASS = 'R';
    SIGNATURE_OBJECT = 'L';
    SIGNATURE_ARRAY = '[';
    SIGNATURE_SUFFIX = ';';

const
    { имена секций и ключей для файла инициализации }
    SECTION_PROGRAMMES = 'programmes';
    KEY_RECENT = 'recent';
    SECTION_WINDOWS = 'windows';
    SECTION_CONTENT = 'content';
    KEY_MAIN = 'main';
    KEY_FILE_BROWSER = 'file browser';
    KEY_DIRECTORY_BROWSER = 'directories browser';
    KEY_RUNNING_PROGRAMMES = 'running programmes';
    SECTION_EMULATOR = 'emulator';
    KEY_AUTO_FULLSCREEN = 'auto fullscreen';
    KEY_AUTO_CLOSEWND = 'auto closewnd';
    KEY_GRAPHIC_SCALING = 'graphic scaling';
    KEY_MAXIMUM_FPS = 'maximum frames';
    KEY_THEME = 'theme';
    SECTION_DEBUGGER_COLORS = 'debugger colors';
    KEY_EMPTY_DATA = 'empty data';
    KEY_NORMAL_DATA = 'normal data';
    KEY_OBJECT_DATA = 'object data';
    KEY_EXCEPT_DATA = 'except data';
    KEY_OLDEBP_DATA = 'oldebp data';
    KEY_RETURN_DATA = 'return data';
    KEY_IRETURN_DATA = 'ireturn data';
    KEY_NORMAL_TYPE = 'normal type';
    KEY_OBJECT_TYPE = 'object type';
    KEY_EXCEPT_TYPE = 'except type';
    KEY_OLDEBP_TYPE = 'oldebp type';
    KEY_RETURN_TYPE = 'return type';
    KEY_IRETURN_TYPE = 'ireturn type';
    KEY_OVERFLOW_TYPE = 'overflow type';
    KEY_FUNCTION_NAME = 'function name';
    KEY_SOURCE_LINE = 'source line';
    KEY_INHERITED = 'inherited from';
    KEY_ALIGNMENT = 'alignment';
    KEY_COMPRESSION = 'compression level';
    KEY_STACK = 'stack size in MB';
    KEY_HEAP = 'heap size in MB';
    KEY_EXECUTABLE = 'executable';

const
    { имена папок с программами }
    DIRECTORY_GAMES = 'games' + DIRECTORY_SEPARATOR;
    DIRECTORY_APPS = 'apps' + DIRECTORY_SEPARATOR;

const
    { цвета отладчика по умолчанию (в формате $RRGGBB) }
    DEFAULT_DEBUGGER_COLOR_EMPTY_DATA = int($c0c0c0);
    DEFAULT_DEBUGGER_COLOR_NORMAL_DATA = int($000000);
    DEFAULT_DEBUGGER_COLOR_OBJECT_DATA = int($800000);
    DEFAULT_DEBUGGER_COLOR_EXCEPT_DATA = int($a0a0a0);
    DEFAULT_DEBUGGER_COLOR_OLDEBP_DATA = int($40a0ff);
    DEFAULT_DEBUGGER_COLOR_RETURN_DATA = int($ffa040);
    DEFAULT_DEBUGGER_COLOR_IRETURN_DATA = int($c07830);
    DEFAULT_DEBUGGER_COLOR_NORMAL_TYPE = int($3078ff);
    DEFAULT_DEBUGGER_COLOR_OBJECT_TYPE = int($3078ff);
    DEFAULT_DEBUGGER_COLOR_EXCEPT_TYPE = int($a0a0a0);
    DEFAULT_DEBUGGER_COLOR_OLDEBP_TYPE = int($40a0ff);
    DEFAULT_DEBUGGER_COLOR_RETURN_TYPE = int($ffa040);
    DEFAULT_DEBUGGER_COLOR_IRETURN_TYPE = int($c07830);
    DEFAULT_DEBUGGER_COLOR_OVERFLOW_TYPE = int($ff0000);
    DEFAULT_DEBUGGER_COLOR_FUNCTION_NAME = int($24905a);
    DEFAULT_DEBUGGER_COLOR_SOURCE_LINE = int($000000);
    DEFAULT_DEBUGGER_COLOR_INHERITED = int($40a0ff);

const
    MAIN_WINDOW_INTERFACE_GUID = '{74C86B60-AC5B-4E8D-8ACD-29A50B5C1519}';
    EMULATION_WINDOW_INTERFACE_GUID = '{74C86B60-AC5B-4E8D-8ACD-29A50B5C151A}';
    REPRESENTER_GUID = '{74C86B60-AC5B-4E8D-8ACD-29A50B5C151B}';

type
    MainWindowInterface = interface;
    EmulationWindowInterface = interface;
    Representer = interface;
    TEmulatorFormWithDialog = class;

    EmulationWindowInterface_Array1d = packed array of EmulationWindowInterface;

    MainWindowInterface = interface(_Interface) [MAIN_WINDOW_INTERFACE_GUID]
        procedure showMainWindow();
        procedure notifyTerminated(window: EmulationWindowInterface);
        procedure saveSettings();
        procedure removeSetting(const section, key: AnsiString);
        procedure setSetting(const section, key, value: AnsiString);
        function isSectionExists(const section: AnsiString): boolean;
        function isKeyExists(const section, key: AnsiString): boolean;
        function getSetting(const section, key, defaultValue: AnsiString): AnsiString;
        function getSections(): AnsiString_Array1d;
        function getKeys(const section: AnsiString): AnsiString_Array1d;
        function getEmulatorsCount(): int;
        function getEmulator(index: int): EmulationWindowInterface;
    end;

    EmulationWindowInterface = interface(ProcessorListener) [EMULATION_WINDOW_INTERFACE_GUID]
        procedure showEmulationWindow();
        procedure showDisassemblerWindow();
        procedure setDebug(debug: boolean);
        function isNowDebugging(): boolean;
        function getMalikProcessor(): MalikProcessor;
        function getMainWindow(): MainWindowInterface;
        function getProgrammeDirectory(): AnsiString;
    end;

    Representer = interface(_Interface) [REPRESENTER_GUID]
        function getUntypedValueRepresentation(): AnsiString;
        function getLongRepresentation(value: long; sourceDataType: int): AnsiString;
        function getFloatRepresentation(value: float): AnsiString;
        function getDoubleRepresentation(value: double): AnsiString;
        function getRealRepresentation(value: real): AnsiString; overload;
        function getRealRepresentation(value: real; sourceDataType: int): AnsiString; overload;
        function getObjectRepresentation(value: int): AnsiString;
        function getHexRepresentation(value: int): AnsiString;
        function getTypeRepresentation(const rawRepresentation: AnsiString): AnsiString;
    end;

    TEmulatorFormWithDialog = class(TForm)
    private
        openedDialog: TForm;
    protected
        procedure lmActivate(var message: TLMActivate); message LM_ACTIVATE;
        function openDialog(dialog: TForm): int; virtual; overload;
        function openDialog(const caption, text: AnsiString; dialogType: TMsgDlgType; buttons: TMsgDlgButtons): int; virtual; overload;
        function getOpenedDialog(): TForm; virtual;
    public
        constructor create(theOwner: TComponent); override;
    end;
{%endregion}

{%region routine }
    function getEmulatorDirectory(): AnsiString;
    function EmulationWindowInterface_Array1d_create(length: int): EmulationWindowInterface_Array1d;
    procedure findBestIconSize(progIcon: TIcon; size: int);
    procedure arraycopy(const src: EmulationWindowInterface_Array1d; srcOffset: int; const dst: EmulationWindowInterface_Array1d; dstOffset: int; length: int); overload;
{%endregion}

implementation

{%region routine }
    function getEmulatorDirectory(): AnsiString;
    var
        i: int;
        s: AnsiString;
    begin
        result := '';
        s := parseParams()[0];
        for i := length(s) downto 1 do begin
            if s[i] = DIRECTORY_SEPARATOR then begin
                result := copy(s, 1, i);
                break;
            end;
        end;
    end;

    function EmulationWindowInterface_Array1d_create(length: int): EmulationWindowInterface_Array1d;
    begin
        setLength(result, length);
    end;

    procedure findBestIconSize(progIcon: TIcon; size: int);
    var
        i: int;
        s: int;
        index: int;
        bestSize: int;
    begin
        if progIcon.count <= 0 then begin
            exit;
        end;
        progIcon.current := 0;
        index := 0;
        bestSize := max(progIcon.width, progIcon.height);
        for i := 1 to progIcon.count - 1 do begin
            progIcon.current := i;
            s := max(progIcon.width, progIcon.height);
            if s <= size then begin
                index := i;
                bestSize := s;
            end;
        end;
        for i := 1 to progIcon.count - 1 do begin
            progIcon.current := i;
            s := max(progIcon.width, progIcon.height);
            if (s > bestSize) and (s <= size) then begin
                index := i;
                bestSize := s;
            end;
        end;
        progIcon.current := index;
    end;

    procedure arraycopy(const src: EmulationWindowInterface_Array1d; srcOffset: int; const dst: EmulationWindowInterface_Array1d; dstOffset: int; length: int);
    var
        lim: int;
        len: int;
        i: int;
    begin
        lim := srcOffset + length;
        len := System.length(src);
        if (lim > len) or (lim < srcOffset) or (srcOffset < 0) or (srcOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        lim := dstOffset + length;
        len := System.length(dst);
        if (lim > len) or (lim < dstOffset) or (dstOffset < 0) or (dstOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        if (src = dst) and (srcOffset < dstOffset) then begin
            for i := length - 1 downto 0 do begin
                dst[dstOffset + i] := src[srcOffset + i];
            end;
        end else begin
            for i := 0 to length - 1 do begin
                dst[dstOffset + i] := src[srcOffset + i];
            end;
        end;
    end;
{%endregion}

{%region TEmulatorFormWithDialog }
    constructor TEmulatorFormWithDialog.create(theOwner: TComponent);
    begin
        inherited create(theOwner);
    end;

    procedure TEmulatorFormWithDialog.lmActivate(var message: TLMActivate);
    var
        dialog: TForm;
    begin
        dialog := getOpenedDialog();
        if (message.active <> 0) and (dialog <> nil) then begin
            dialog.showOnTop();
        end;
    end;

    function TEmulatorFormWithDialog.openDialog(dialog: TForm): int;
    var
        capture: int;
    begin
        if dialog = nil then begin
            raise NullPointerException.create('TEmulatorFormWithDialog.openDialog: dialog = nil.');
        end;
        if openedDialog <> nil then begin
            raise EInvalidOperation.create('TEmulatorFormWithDialog.openDialog: у этой формы уже открыто диалоговое окно.');
        end;
        if self.enabled = false then begin
            raise EInvalidOperation.create('TEmulatorFormWithDialog.openDialog: эта форма недоступна.');
        end;
        if dialog.visible = true then begin
            raise EInvalidOperation.create('TEmulatorFormWithDialog.openDialog: запрашиваемое диалоговое окно уже открыто.');
        end;
        if dialog.enabled = false then begin
            raise EInvalidOperation.create('TEmulatorFormWithDialog.openDialog: запрашиваемое диалоговое окно недоступно.');
        end;
        if dialog.formStyle = TFormStyle.fsMDIChild then begin
            raise EInvalidOperation.create('TEmulatorFormWithDialog.openDialog: запрашиваемое окно не может быть диалоговым по своей природе.');
        end;
        dragManager.dragStop(false);
        if activePopupMenu <> nil then begin
            activePopupMenu.close();
        end;
        capture := getCapture();
        if capture <> 0 then begin
            sendMessage(capture, LM_CANCELMODE, 0, 0);
        end;
        releaseCapture();
        result := 0;
        openedDialog := dialog;
        try
            dialog.position := poDesigned;
            dialog.left := max(0, min(left + ((self.width - dialog.width) div 2), screen.desktopWidth - dialog.width));
            dialog.top := max(0, min(top + ((self.height - dialog.height) div 2), screen.desktopHeight - dialog.height));
            dialog.modalResult := 0;
            dialog.show();
            self.enabled := false;
            try
                repeat
                    try
                        widgetSet.appProcessMessages();
                    except
                        if application.captureExceptions then begin
                            application.handleException(self);
                        end else begin
                            raise;
                        end;
                    end;
                    if application.terminated then begin
                        dialog.modalResult := 0;
                        break;
                    end;
                    if dialog.modalResult <> 0 then begin
                        dialog.hide();
                        break;
                    end;
                    if dialog.visible = false then begin
                        break;
                    end;
                    application.idle(true);
                until false;
                result := dialog.modalResult;
                if result = 0 then begin
                    result := mrClose;
                end;
            finally
                self.enabled := true;
                self.show();
            end;
        finally
            openedDialog := nil;
        end;
    end;

    function TEmulatorFormWithDialog.openDialog(const caption, text: AnsiString; dialogType: TMsgDlgType; buttons: TMsgDlgButtons): int;
    var
        dialog: TForm;
    begin
        dialog := createMessageDialog(text, dialogType, buttons);
        try
            dialog.caption := caption;
            result := openDialog(dialog);
        finally
            dialog.free();
        end;
    end;

    function TEmulatorFormWithDialog.getOpenedDialog(): TForm;
    var
        dialog: TForm;
    begin
        result := openedDialog;
        while result is TEmulatorFormWithDialog do begin
            dialog := TEmulatorFormWithDialog(result).openedDialog;
            if dialog = nil then begin
                break;
            end;
            result := dialog;
        end;
    end;
{%endregion}

end.

