{
    Manifests содержит классы для работы с метаданными о программах,
    которые обычно хранятся в архивах с программами (/META-INF/MANIFEST.MF).

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

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

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

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

unit Manifests;

{$MODE DELPHI}

interface

uses
    Lang,
    IOStreams,
    FileIO;

{%region public }
type
    ProgrammeManifestProperty = class;
    ProgrammeManifest = class;

    ProgrammeManifestProperty_Array1d = packed array of ProgrammeManifestProperty;

    ProgrammeManifestProperty = class(_Object)
    strict private
        name: AnsiString;
        value: AnsiString;
    public
        constructor create(); overload;
        constructor create(const name: AnsiString); overload;
        constructor create(const name, value: AnsiString); overload;
        procedure assign(prop: ProgrammeManifestProperty); overload;
        procedure assign(const name, value: AnsiString); overload;
        procedure setValue(const value: AnsiString);
        function getName(): AnsiString;
        function getValue(): AnsiString;
        function getValueComponents(): AnsiString_Array1d;
    end;

    ProgrammeManifest = class(_Object)
    strict private
        length: int;
        properties: ProgrammeManifestProperty_Array1d;
        procedure add(prop: ProgrammeManifestProperty);
        procedure remove(index: int);
    public
        constructor create();
        destructor destroy; override;
        procedure loadFromStream(stream: Input); virtual;
        procedure saveToStream(stream: Output); virtual;
        procedure loadFromFile(const fileName: AnsiString); overload;
        procedure loadFromFile(const fileName: UnicodeString); overload;
        procedure saveToFile(const fileName: AnsiString); overload;
        procedure saveToFile(const fileName: UnicodeString); overload;
        procedure clear();
        procedure setValue(const name, value: AnsiString);
        procedure setProperty(index: int; prop: ProgrammeManifestProperty);
        function getValue(const name: AnsiString): AnsiString;
        function getProperty(index: int): ProgrammeManifestProperty;
        function getLength(): int;
        function indexOf(const name: AnsiString): int;
        function find(const name: AnsiString): ProgrammeManifestProperty;

    strict private const
        MAX_LINE_LENGTH = int(100);
    end;
{%endregion}

{%region routine }
    function getComponents(const s: AnsiString): AnsiString_Array1d; overload;
    function getComponents(const s: AnsiString; separator: Char): AnsiString_Array1d; overload;
    function makeStringOf(const c: AnsiString_Array1d): AnsiString; overload;
    function makeStringOf(const c: AnsiString_Array1d; separator: Char): AnsiString; overload;
    function ProgrammeManifestProperty_Array1d_create(length: int): ProgrammeManifestProperty_Array1d;
    procedure arraycopy(const src: ProgrammeManifestProperty_Array1d; srcOffset: int; const dst: ProgrammeManifestProperty_Array1d; dstOffset: int; length: int); overload;
{%endregion}

implementation

{%region routine }
    function getComponents(const s: AnsiString): AnsiString_Array1d;
    begin
        result := getComponents(s, ',');
    end;

    function getComponents(const s: AnsiString; separator: Char): AnsiString_Array1d;
    var
        c: int;
        i: int;
        j: int;
        len: int;
    begin
        if trim(s) = '' then begin
            result := nil;
            exit;
        end;
        c := 1;
        i := 1;
        len := length(s);
        for i := 1 to len do begin
            if s[i] = separator then begin
                inc(c);
            end;
        end;
        result := String_Array1d_create(c);
        c := 0;
        i := 1;
        j := 1;
        for i := 1 to len do begin
            if s[i] = separator then begin
                result[c] := trim(copy(s, j, i - j));
                inc(c);
                j := i + 1;
            end;
        end;
        result[c] := trim(copy(s, j, len - j + 1));
    end;

    function makeStringOf(const c: AnsiString_Array1d): AnsiString;
    begin
        result := makeStringOf(c, ',');
    end;

    function makeStringOf(const c: AnsiString_Array1d; separator: Char): AnsiString;
    var
        i: int;
        j: int;
    begin
        result := '';
        j := length(c) - 1;
        for i := 0 to j do begin
            if i < j then begin
                result := result + trim(c[i]) + separator;
            end else begin
                result := result + trim(c[i]);
            end;
        end;
    end;

    function ProgrammeManifestProperty_Array1d_create(length: int): ProgrammeManifestProperty_Array1d;
    begin
        setLength(result, length);
    end;

    procedure arraycopy(const src: ProgrammeManifestProperty_Array1d; srcOffset: int; const dst: ProgrammeManifestProperty_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 ProgrammeManifestProperty }
    constructor ProgrammeManifestProperty.create();
    begin
        inherited create();
        self.name := '';
        self.value := '';
    end;

    constructor ProgrammeManifestProperty.create(const name: AnsiString);
    begin
        inherited create();
        self.name := name;
        self.value := '';
    end;

    constructor ProgrammeManifestProperty.create(const name, value: AnsiString);
    begin
        inherited create();
        self.name := name;
        self.value := value;
    end;

    procedure ProgrammeManifestProperty.assign(prop: ProgrammeManifestProperty);
    begin
        self.name := prop.name;
        self.value := prop.value;
    end;

    procedure ProgrammeManifestProperty.assign(const name, value: AnsiString);
    begin
        self.name := name;
        self.value := value;
    end;

    procedure ProgrammeManifestProperty.setValue(const value: AnsiString);
    begin
        self.value := value;
    end;

    function ProgrammeManifestProperty.getName(): AnsiString;
    begin
        result := name;
    end;

    function ProgrammeManifestProperty.getValue(): AnsiString;
    begin
        result := value;
    end;

    function ProgrammeManifestProperty.getValueComponents(): AnsiString_Array1d;
    begin
        result := getComponents(value);
    end;
{%endregion}

{%region ProgrammeManifest }
    constructor ProgrammeManifest.create();
    begin
        inherited create();
        length := 0;
        properties := ProgrammeManifestProperty_Array1d_create(16);
    end;

    destructor ProgrammeManifest.destroy;
    var
        i: int;
        p: ProgrammeManifestProperty_Array1d;
    begin
        p := properties;
        for i := length - 1 downto 0 do begin
            p[i].free();
        end;
        inherited destroy;
    end;

    procedure ProgrammeManifest.add(prop: ProgrammeManifestProperty);
    var
        p: ProgrammeManifestProperty_Array1d;
        len: int;
    begin
        p := properties;
        len := length;
        if len = System.length(p) then begin
            p := ProgrammeManifestProperty_Array1d_create(len shl 1);
            arraycopy(properties, 0, p, 0, len);
            properties := p;
        end;
        p[len] := prop;
        length := len + 1;
    end;

    procedure ProgrammeManifest.remove(index: int);
    var
        p: ProgrammeManifestProperty_Array1d;
        len: int;
        c: int;
    begin
        p := properties;
        len := length - 1;
        p[index].free();
        c := len - index;
        if c > 0 then begin
            arraycopy(p, index + 1, p, index, c);
        end;
        length := len;
        p[len] := nil;
    end;

    procedure ProgrammeManifest.loadFromStream(stream: Input);
    var
        p: byte_Array1d;
        l: int;
        prev: int;
        curr: int;
        name: AnsiString;
        value: AnsiString;
    begin
        l := int(stream.available());
        p := byte_Array1d_create(l + 1);
        stream.read(p, 0, l);
        p[l] := byte($00);
        if (p[0] = byte($ef)) and (p[1] = byte($bb)) and (p[2] = byte($bf)) then begin
            curr := 3;
        end else begin
            curr := 0;
        end;
        clear();
        while p[curr] <> byte($00) do begin
            prev := curr;
            if Char(p[curr]) in ['0'..'9', 'A'..'Z', 'a'..'z'] then begin
                inc(curr)
            end else begin
                break;
            end;
            while Char(p[curr]) in ['0'..'9', 'A'..'Z', 'a'..'z', '-', '_'] do begin
                inc(curr);
            end;
            name := extractString(p, prev, curr - prev);
            if Char(p[curr]) = ':' then begin
                inc(curr);
            end;
            value := '';
            repeat
                if p[curr] = byte($20) then begin
                    inc(curr);
                end;
                prev := curr;
                while not(Char(p[curr]) in [#0, #10, #13]) do begin
                    inc(curr);
                end;
                value := value + extractString(p, prev, curr - prev);
                while Char(p[curr]) in [#10, #13] do begin
                    inc(curr);
                end;
            until p[curr] <> byte($20);
            setValue(name, value);
        end;
    end;

    procedure ProgrammeManifest.saveToStream(stream: Output);
    var
        i: int;
        j: int;
        k: int;
        c: int;
        p: ProgrammeManifestProperty_Array1d;
        stored: UnicodeString;
        wideName: UnicodeString;
        wideValue: UnicodeString;
    begin
        p := properties;
        stored := '';
        for i := 0 to length - 1 do begin
            with p[i] do begin
                wideName := toUTF16String(getName());
                wideValue := toUTF16String(getValue());
            end;
            stored := stored + wideName + ':';
            j := MAX_LINE_LENGTH - (System.length(wideName) + 2);
            if j < 0 then begin
                j := 0;
            end;
            k := 0;
            repeat
                stored := stored + #$0020 + copy(wideValue, k + 1, j - k);
                c := int(stored[System.length(stored)]);
                if (c >= $d800) and (c <= $dbff) then begin
                    inc(j);
                    stored := stored + wideValue[j] + LINE_ENDING;
                end else begin
                    stored := stored + LINE_ENDING;
                end;
                k := j;
                inc(j, MAX_LINE_LENGTH - 1);
            until k > System.length(wideValue);
        end;
        stream.write(stringToByteArray(toUTF8String(stored)));
    end;

    procedure ProgrammeManifest.loadFromFile(const fileName: AnsiString);
    var
        f: FileInputStream;
    begin
        f := FileInputStream.create(fileName);
        if f.isInvalidHandle() then begin
            f.free();
            raise FileNotOpenException.create(fileName);
        end;
        loadFromStream(f);
    end;

    procedure ProgrammeManifest.loadFromFile(const fileName: UnicodeString);
    var
        f: FileInputStream;
    begin
        f := FileInputStream.create(fileName);
        if f.isInvalidHandle() then begin
            f.free();
            raise FileNotOpenException.create(fileName);
        end;
        loadFromStream(f);
    end;

    procedure ProgrammeManifest.saveToFile(const fileName: AnsiString);
    var
        f: FileOutputStream;
    begin
        f := FileOutputStream.create(fileName);
        if f.isInvalidHandle() then begin
            f.free();
            raise FileNotOpenException.create(fileName);
        end;
        saveToStream(f);
    end;

    procedure ProgrammeManifest.saveToFile(const fileName: UnicodeString);
    var
        f: FileOutputStream;
    begin
        f := FileOutputStream.create(fileName);
        if f.isInvalidHandle() then begin
            f.free();
            raise FileNotOpenException.create(fileName);
        end;
        saveToStream(f);
    end;

    procedure ProgrammeManifest.clear();
    var
        i: int;
        p: ProgrammeManifestProperty_Array1d;
    begin
        p := properties;
        for i := length - 1 downto 0 do begin
            p[i].free();
            p[i] := nil;
        end;
        length := 0;
    end;

    procedure ProgrammeManifest.setValue(const name, value: AnsiString);
    var
        i: int;
    begin
        i := indexOf(name);
        if System.length(value) > 0 then begin
            if i >= 0 then begin
                properties[i].setValue(value);
            end else begin
                add(ProgrammeManifestProperty.create(name, value));
            end;
        end else begin
            if i >= 0 then begin
                remove(i);
            end;
        end;
    end;

    procedure ProgrammeManifest.setProperty(index: int; prop: ProgrammeManifestProperty);
    begin
        getProperty(index).assign(prop);
    end;

    function ProgrammeManifest.getValue(const name: AnsiString): AnsiString;
    var
        i: int;
    begin
        i := indexOf(name);
        if i >= 0 then begin
            result := properties[i].getValue();
        end else begin
            result := '';
        end;
    end;

    function ProgrammeManifest.getProperty(index: int): ProgrammeManifestProperty;
    begin
        if (index < 0) or (index >= length) then begin
            raise IndexOutOfBoundsException.create('ProgrammeManifest.getProperty: индекс выходит из диапазона.');
        end;
        result := properties[index];
    end;

    function ProgrammeManifest.getLength(): int;
    begin
        result := length;
    end;

    function ProgrammeManifest.indexOf(const name: AnsiString): int;
    var
        i: int;
        p: ProgrammeManifestProperty_Array1d;
    begin
        p := properties;
        for i := 0 to length - 1 do begin
            if p[i].getName() = name then begin
                result := i;
                exit;
            end;
        end;
        result := -1;
    end;

    function ProgrammeManifest.find(const name: AnsiString): ProgrammeManifestProperty;
    var
        i: int;
    begin
        i := indexOf(name);
        if i >= 0 then begin
            result := properties[i];
        end else begin
            result := nil;
        end;
    end;
{%endregion}

end.

