{
    EmulProgrammes содержит классы для хранения метаданных об установленных
    на Малик Эмулятор программах.
    Этот исходный текст является частью Малик Эмулятора.

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

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

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

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

unit EmulProgrammes;

{$MODE DELPHI}

interface

uses
    Classes,
    Forms,
    Graphics,
    Lang,
    FileIO,
    Manifests,
    EmulConstants;

{%region public }
type
    ProgrammeInfo = class;

    ProgrammeInfo_Array1d = packed array of ProgrammeInfo;

    ProgrammeInfo = class(_Object)
    strict private
        debugInfoFileName: AnsiString;
        executableFileName: AnsiString;
        iconFileName: AnsiString;
        manifestFileName: AnsiString;
        programmeDirectory: AnsiString;
        screenSizes: int;
        exists: boolean;
        procedure setScreenSizes(manifest: ProgrammeManifest); overload;
    public
        constructor create(const directory: AnsiString);
        procedure saveManifest(manifest: ProgrammeManifest);
        procedure loadManifest(manifest: ProgrammeManifest);
        procedure loadIcon(icon: Graphics.TIcon);
        procedure setScreenSizes(width, height: int); overload;
        function resourceNameToFileName(const resName: AnsiString): AnsiString;
        function getDebugInfoFileName(): AnsiString;
        function getExecutableFileName(): AnsiString;
        function getIconFileName(): AnsiString;
        function getManifestFileName(): AnsiString;
        function getProgrammeDirectory(): AnsiString;
        function getScreenWidth(): int;
        function getScreenHeight(): int;
        function isDirectoryExists(): boolean;

    public const
        MANIFEST_FILE_NAME = 'META-INF' + DIRECTORY_SEPARATOR + 'MANIFEST.MF';
    end;
{%endregion}

{%region routine }
    function virtualFileNameToOSFileName(const fileName: AnsiString): AnsiString; overload;
    function virtualFileNameToOSFileName(const fileName: UnicodeString): UnicodeString; overload;
    function ProgrammeInfo_Array1d_create(length: int): ProgrammeInfo_Array1d;
    procedure arraycopy(const src: ProgrammeInfo_Array1d; srcOffset: int; const dst: ProgrammeInfo_Array1d; dstOffset: int; length: int); overload;
{%endregion}

implementation

{%region routine }
    function virtualFileNameToOSFileName(const fileName: AnsiString): AnsiString;
    var
        i: int;
    begin
        if (DIRECTORY_SEPARATOR <> '/') and (pos(DIRECTORY_SEPARATOR, fileName) > 0) then begin
            result := copy(fileName, 1, length(fileName));
            for i := 1 to length(result) do begin
                if result[i] = '/' then begin
                    result[i] := DIRECTORY_SEPARATOR;
                end;
            end;
            exit;
        end;
        result := fileName;
    end;

    function virtualFileNameToOSFileName(const fileName: UnicodeString): UnicodeString;
    var
        i: int;
    begin
        if (DIRECTORY_SEPARATOR <> '/') and (pos(DIRECTORY_SEPARATOR, fileName) > 0) then begin
            result := copy(fileName, 1, length(fileName));
            for i := 1 to length(result) do begin
                if result[i] = '/' then begin
                    result[i] := DIRECTORY_SEPARATOR;
                end;
            end;
            exit;
        end;
        result := fileName;
    end;

    function ProgrammeInfo_Array1d_create(length: int): ProgrammeInfo_Array1d;
    begin
        setLength(result, length);
    end;

    procedure arraycopy(const src: ProgrammeInfo_Array1d; srcOffset: int; const dst: ProgrammeInfo_Array1d; dstOffset: int; length: int);
    var
        lim: int;
        len: 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;
        move(src[srcOffset], dst[dstOffset], length * sizeof(_Object));
    end;
{%endregion}

{%region ProgrammeInfo }
    constructor ProgrammeInfo.create(const directory: AnsiString);
    var
        m: ProgrammeManifest;
        e: AnsiString;
        s: AnsiString;
    begin
        inherited create();
        programmeDirectory := directory;
        m := ProgrammeManifest.create();
        try
            manifestFileName := directory + MANIFEST_FILE_NAME;
            exists := fileExists(manifestFileName);
            if not exists then begin
                exit;
            end;
            try
                m.loadFromFile(manifestFileName);
            except
                exit;
            end;
            e := m.getValue(MANIFEST_PROPERTY_PROGRAMME_EXECUTABLE);
            if (length(e) > 0) and (e[1] = '/') then begin
                delete(e, 1, 1);
            end;
            e := virtualFileNameToOSFileName(e);
            executableFileName := directory + e + '.mal';
            s := m.getValue(MANIFEST_PROPERTY_PROGRAMME_ICON);
            if length(s) > 0 then begin
                if (length(s) > 0) and (s[1] = '/') then begin
                    delete(s, 1, 1);
                end;
                iconFileName := directory + virtualFileNameToOSFileName(s);
            end else begin
                iconFileName := directory + e + '.ico';
            end;
            debugInfoFileName := directory + e + '.dbg';
            setScreenSizes(m);
        finally
            m.free();
        end;
    end;

    procedure ProgrammeInfo.setScreenSizes(manifest: ProgrammeManifest);
    var
        sizesProperty: ProgrammeManifestProperty;
        sizes: AnsiString_Array1d;
        w: int;
        h: int;
    begin
        sizesProperty := manifest.find(MANIFEST_PROPERTY_MALIK_SCREEN_SIZE);
        if sizesProperty <> nil then begin
            w := length(sizesProperty.getValue());
            if (w >= 3) and (w < 9) then begin
                sizes := sizesProperty.getValueComponents();
                if length(sizes) = 2 then begin
                    w := parseDecInt(sizes[0], DEFAULT_SCREEN_WIDTH);
                    h := parseDecInt(sizes[1], DEFAULT_SCREEN_HEIGHT);
                    screenSizes := (h shl 16) + (w and $ffff);
                    exit;
                end;
            end;
        end;
        screenSizes := (DEFAULT_SCREEN_HEIGHT shl 16) + (DEFAULT_SCREEN_WIDTH and $ffff);
    end;

    procedure ProgrammeInfo.saveManifest(manifest: ProgrammeManifest);
    begin
        manifest.saveToFile(manifestFileName);
    end;

    procedure ProgrammeInfo.loadManifest(manifest: ProgrammeManifest);
    begin
        manifest.loadFromFile(manifestFileName);
    end;

    procedure ProgrammeInfo.loadIcon(icon: Graphics.TIcon);
    var
        hs: Classes.THandleStream;
    begin
        with FileInputStream.create(iconFileName) do begin
            try
                if isInvalidHandle() then begin
                    icon.assign(Forms.application.icon);
                    exit;
                end;
                hs := Classes.THandleStream.create(getHandle());
                try
                    icon.loadFromStream(hs);
                finally
                    hs.free();
                end;
            finally
                free();
            end;
        end;
    end;

    procedure ProgrammeInfo.setScreenSizes(width, height: int);
    begin
        screenSizes := (height shl 16) + (width and $ffff);
    end;

    function ProgrammeInfo.resourceNameToFileName(const resName: AnsiString): AnsiString;
    begin
        if (length(resName) > 0) and (resName[1] = '/') then begin
            result := programmeDirectory + virtualFileNameToOSFileName(copy(resName, 2, length(resName) - 1));
        end else begin
            result := programmeDirectory + virtualFileNameToOSFileName(resName);
        end;
    end;

    function ProgrammeInfo.getDebugInfoFileName(): AnsiString;
    begin
        result := debugInfoFileName;
    end;

    function ProgrammeInfo.getExecutableFileName(): AnsiString;
    begin
        result := executableFileName;
    end;

    function ProgrammeInfo.getIconFileName(): AnsiString;
    begin
        result := iconFileName;
    end;

    function ProgrammeInfo.getManifestFileName(): AnsiString;
    begin
        result := manifestFileName;
    end;

    function ProgrammeInfo.getProgrammeDirectory(): AnsiString;
    begin
        result := programmeDirectory;
    end;

    function ProgrammeInfo.getScreenWidth(): int;
    begin
        result := screenSizes and $ffff;
    end;

    function ProgrammeInfo.getScreenHeight(): int;
    begin
        result := (screenSizes shr 16) and $ffff;
    end;

    function ProgrammeInfo.isDirectoryExists(): boolean;
    begin
        result := exists;
    end;
{%endregion}

end.

