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

import java.io.*;
import java.util.*;
import malik.emulator.fileformats.font.*;
import malik.emulator.util.*;

public final class SystemFont extends Object
{
    private static class Enumerator extends Object implements Enumeration
    {
        public SystemFont current;

        public Enumerator(SystemFont first) {
            this.current = first;
        }

        public boolean hasMoreElements() {
            return current != null;
        }

        public Object nextElement() {
            SystemFont result;
            if((result = current) == null)
            {
                throw new NoSuchElementException("Enumeration.nextElement: элементов больше не осталось.");
            }
            current = result.getNext();
            return result;
        }
    }

    public static final int STYLE_BOLD = 0x01;
    public static final int STYLE_ITALIC = 0x02;

    private static final SystemFont FIRST_SYSTEM;
    private static final SystemFont LAST_SYSTEM;
    private static final Object MONITOR;

    static {
        SystemFont last = null;
        SystemFont first = null;
        for(int handle = findNextHandle(0); handle != 0; handle = findNextHandle(handle))
        {
            SystemFont font = new SystemFont(handle, true);
            last = last != null ? ((font.prev = last).next = font) : (first = font);
        }
        FIRST_SYSTEM = first;
        LAST_SYSTEM = last;
        MONITOR = new Object();
    }

    public static Enumeration systemFonts() {
        final SystemFont last = LAST_SYSTEM;
        return new Enumerator(FIRST_SYSTEM) {
            public Object nextElement() {
                SystemFont result;
                if((result = current) == null)
                {
                    throw new NoSuchElementException("Enumeration.nextElement: элементов больше не осталось.");
                }
                current = result == last ? null : result.getNext();
                return result;
            }
        };
    }

    public static Enumeration installedFonts() {
        return new Enumerator(LAST_SYSTEM.next);
    }

    public static Enumeration availableFonts() {
        return new Enumerator(FIRST_SYSTEM);
    }

    public static SystemFont get(String fontName) {
        for(SystemFont font = FIRST_SYSTEM; font != null; font = font.next) if(fontName == null ? font.name == null : fontName.equals(font.name)) return font;
        return FIRST_SYSTEM;
    }

    public static SystemFont getDefault() {
        return FIRST_SYSTEM;
    }

    public static SystemFont install(String fontName, String fileName) throws IOException {
        int len;
        int handle;
        char[] nameOfFile;
        char[] nameOfFont;
        SystemFont result;
        SystemFont sfont1;
        SystemFont sfont2;
        if(fileName == null)
        {
            throw new NullPointerException("SystemFont.install: аргумент fileName равен нулевой ссылке.");
        }
        if(fontName == null)
        {
            int pos1 = fileName.lastIndexOf('/') + 1;
            int pos2 = fileName.lastIndexOf('.');
            fontName = pos1 <= pos2 ? fileName.substring(pos1, pos2) : fileName.substring(pos1);
        }
        fileName.getChars(0, len = fileName.length(), nameOfFile = new char[len + 1], 0);
        fontName.getChars(0, len = fontName.length(), nameOfFont = new char[len + 1], 0);
        if((handle = (int) MalikSystem.syscall(Array.getFirstElementAddress(nameOfFont), Array.getFirstElementAddress(nameOfFile), 0x002e)) == 0)
        {
            throw new IOException((new StringBuilder()).append("SystemFont.install: ошибка установки шрифта из ").append(fileName).append('.').toString());
        }
        result = new SystemFont(handle, false);
        synchronized(MONITOR)
        {
            sfont2 = (sfont1 = LAST_SYSTEM).next;
            result.prev = sfont1;
            result.next = sfont2;
            sfont1.next = result;
            if(sfont2 != null) sfont2.prev = result;
        }
        return result;
    }

    public static SystemFont install(String fontName, UnicodeRasterFont font) throws IOException {
        String fileName;
        if(font == null)
        {
            throw new NullPointerException("SystemFont.install: аргумент font равен нулевой ссылке.");
        }
        if((fileName = font.getLastFileName()) == null)
        {
            throw new IllegalStateException("SystemFont.install: объект font не содержит имени файла.");
        }
        return install(fontName, fileName);
    }

    static Object monitor() {
        return MONITOR;
    }

    private static int findNextHandle(int handle) {
        return (int) MalikSystem.syscall((long) handle, 0x0028);
    }

    private boolean installed;
    private final boolean system;
    private final int style;
    private final int handle;
    private final int height;
    private final int baselineHeight;
    private final int baselinePosition;
    private final StringDrawDescriptor descriptor;
    private final String name;
    private SystemFont prev;
    private SystemFont next;

    private SystemFont(int handle, boolean system) {
        int h;
        int b;
        int s;
        char[] name;
        s = (int) MalikSystem.syscall((long) handle, 0x002a);
        MalikSystem.syscall(handle, Array.getFirstElementAddress(name = new char[(int) MalikSystem.syscall(handle, 0, 0x0029)]), 0x0029);
        this.installed = true;
        this.system = system;
        this.style = s & (STYLE_BOLD | STYLE_ITALIC);
        this.handle = handle;
        this.height = h = (s >> 8) & 0xff;
        this.baselineHeight = b = (s >> 16) & 0xff;
        this.baselinePosition = h - b;
        this.descriptor = new StringDrawDescriptor();
        this.name = new String(name);
    }

    public String toString() {
        int s = style;
        return (new StringBuilder()).
            append("Системный шрифт[\"").append(name).append("\", размер=").append(height).
            append((s & STYLE_BOLD) != 0 ? ", жирный" : "").append((s & STYLE_ITALIC) != 0 ? ", курсив" : "").append("]").
        toString();
    }

    public void uninstall() {
        if(system)
        {
            throw new IllegalStateException("SystemFont.uninstall: нельзя удалить системный шрифт.");
        }
        synchronized(MONITOR)
        {
            if(installed)
            {
                SystemFont sfont1;
                SystemFont sfont2;
                MalikSystem.syscall((long) handle, 0x002f);
                installed = false;
                sfont1 = prev;
                sfont2 = next;
                prev = next = null;
                sfont1.next = sfont2;
                if(sfont2 != null) sfont2.prev = sfont1;
            }
        }
    }

    public boolean isInstalled() {
        return installed;
    }

    public boolean isSystem() {
        return system;
    }

    public boolean isBold() {
        return (style & STYLE_BOLD) != 0;
    }

    public boolean isItalic() {
        return (style & STYLE_ITALIC) != 0;
    }

    public boolean characterSupported(int charCode) {
        boolean result;
        int error;
        if(system) return (int) MalikSystem.syscall(handle, charCode, 0x002b) != 0;
        error = 0;
        synchronized(MONITOR)
        {
            label0:
            {
                if(!installed)
                {
                    error = 1;
                    result = false;
                    break label0;
                }
                result = (int) MalikSystem.syscall(handle, charCode, 0x002b) != 0;
            }
        }
        if(error == 1)
        {
            throw new UninstalledFontException("SystemFont.characterSupported: этот шрифт был удалён из системы.");
        }
        return result;
    }

    public int characterWidth(int charCode) {
        int error;
        int result;
        StringDrawDescriptor d;
        if(system)
        {
            synchronized(d = descriptor)
            {
                d.assignString(false, MalikSystem.getLocalVariableAddress(charCode), 1, handle);
                result = (int) MalikSystem.syscall((long) d.getDescriptorAddress(), 0x002c);
            }
            return result;
        }
        error = 0;
        synchronized(MONITOR)
        {
            label0:
            {
                if(!installed)
                {
                    error = 1;
                    result = 0;
                    break label0;
                }
                synchronized(d = descriptor)
                {
                    d.assignString(false, MalikSystem.getLocalVariableAddress(charCode), 1, handle);
                    result = (int) MalikSystem.syscall((long) d.getDescriptorAddress(), 0x002c);
                }
            }
        }
        if(error == 1)
        {
            throw new UninstalledFontException("SystemFont.characterWidth: этот шрифт был удалён из системы.");
        }
        return result;
    }

    public int charactersWidth(char[] src) {
        int error;
        int result;
        StringDrawDescriptor d;
        if(src == null)
        {
            throw new NullPointerException("SystemFont.charactersWidth: аргумент src равен нулевой ссылке.");
        }
        if(system)
        {
            synchronized(d = descriptor)
            {
                d.assignString(true, Array.getFirstElementAddress(src), src.length, handle);
                result = (int) MalikSystem.syscall((long) d.getDescriptorAddress(), 0x002c);
            }
            return result;
        }
        error = 0;
        synchronized(MONITOR)
        {
            label0:
            {
                if(!installed)
                {
                    error = 1;
                    result = 0;
                    break label0;
                }
                synchronized(d = descriptor)
                {
                    d.assignString(true, Array.getFirstElementAddress(src), src.length, handle);
                    result = (int) MalikSystem.syscall((long) d.getDescriptorAddress(), 0x002c);
                }
            }
        }
        if(error == 1)
        {
            throw new UninstalledFontException("SystemFont.charactersWidth: этот шрифт был удалён из системы.");
        }
        return result;
    }

    public int charactersWidth(char[] src, int offset, int length) {
        int error;
        int result;
        StringDrawDescriptor d;
        if(src == null)
        {
            throw new NullPointerException("SystemFont.charactersWidth: аргумент src равен нулевой ссылке.");
        }
        Array.checkBound("SystemFont.charactersWidth", src.length, offset, length);
        if(system)
        {
            synchronized(d = descriptor)
            {
                d.assignString(true, Array.getFirstElementAddress(src) + (offset << 1), length, handle);
                result = (int) MalikSystem.syscall((long) d.getDescriptorAddress(), 0x002c);
            }
            return result;
        }
        error = 0;
        synchronized(MONITOR)
        {
            label0:
            {
                if(!installed)
                {
                    error = 1;
                    result = 0;
                    break label0;
                }
                synchronized(d = descriptor)
                {
                    d.assignString(true, Array.getFirstElementAddress(src) + (offset << 1), length, handle);
                    result = (int) MalikSystem.syscall((long) d.getDescriptorAddress(), 0x002c);
                }
            }
        }
        if(error == 1)
        {
            throw new UninstalledFontException("SystemFont.charactersWidth: этот шрифт был удалён из системы.");
        }
        return result;
    }

    public int charactersWidth(int[] src) {
        int error;
        int result;
        StringDrawDescriptor d;
        if(src == null)
        {
            throw new NullPointerException("SystemFont.charactersWidth: аргумент src равен нулевой ссылке.");
        }
        if(system)
        {
            synchronized(d = descriptor)
            {
                d.assignString(false, Array.getFirstElementAddress(src), src.length, handle);
                result = (int) MalikSystem.syscall((long) d.getDescriptorAddress(), 0x002c);
            }
            return result;
        }
        error = 0;
        synchronized(MONITOR)
        {
            label0:
            {
                if(!installed)
                {
                    error = 1;
                    result = 0;
                    break label0;
                }
                synchronized(d = descriptor)
                {
                    d.assignString(false, Array.getFirstElementAddress(src), src.length, handle);
                    result = (int) MalikSystem.syscall((long) d.getDescriptorAddress(), 0x002c);
                }
            }
        }
        if(error == 1)
        {
            throw new UninstalledFontException("SystemFont.charactersWidth: этот шрифт был удалён из системы.");
        }
        return result;
    }

    public int charactersWidth(int[] src, int offset, int length) {
        int error;
        int result;
        StringDrawDescriptor d;
        if(src == null)
        {
            throw new NullPointerException("SystemFont.charactersWidth: аргумент src равен нулевой ссылке.");
        }
        Array.checkBound("SystemFont.charactersWidth", src.length, offset, length);
        if(system)
        {
            synchronized(d = descriptor)
            {
                d.assignString(false, Array.getFirstElementAddress(src) + (offset << 2), length, handle);
                result = (int) MalikSystem.syscall((long) d.getDescriptorAddress(), 0x002c);
            }
            return result;
        }
        error = 0;
        synchronized(MONITOR)
        {
            label0:
            {
                if(!installed)
                {
                    error = 1;
                    result = 0;
                    break label0;
                }
                synchronized(d = descriptor)
                {
                    d.assignString(false, Array.getFirstElementAddress(src) + (offset << 2), length, handle);
                    result = (int) MalikSystem.syscall((long) d.getDescriptorAddress(), 0x002c);
                }
            }
        }
        if(error == 1)
        {
            throw new UninstalledFontException("SystemFont.charactersWidth: этот шрифт был удалён из системы.");
        }
        return result;
    }

    public int stringWidth(String string) {
        int error;
        int result;
        StringDrawDescriptor d;
        if(string == null)
        {
            throw new NullPointerException("SystemFont.stringWidth: аргумент string равен нулевой ссылке.");
        }
        if(system)
        {
            synchronized(d = descriptor)
            {
                d.assignString(true, Array.getFirstElementAddress(string), string.length(), handle);
                result = (int) MalikSystem.syscall((long) d.getDescriptorAddress(), 0x002c);
            }
            return result;
        }
        error = 0;
        synchronized(MONITOR)
        {
            label0:
            {
                if(!installed)
                {
                    error = 1;
                    result = 0;
                    break label0;
                }
                synchronized(d = descriptor)
                {
                    d.assignString(true, Array.getFirstElementAddress(string), string.length(), handle);
                    result = (int) MalikSystem.syscall((long) d.getDescriptorAddress(), 0x002c);
                }
            }
        }
        if(error == 1)
        {
            throw new UninstalledFontException("SystemFont.stringWidth: этот шрифт был удалён из системы.");
        }
        return result;
    }

    public int substringWidth(String string, int offset, int length) {
        int error;
        int result;
        StringDrawDescriptor d;
        if(string == null)
        {
            throw new NullPointerException("SystemFont.substringWidth: аргумент string равен нулевой ссылке.");
        }
        String.checkBound("SystemFont.substringWidth", string.length(), offset, length);
        if(system)
        {
            synchronized(d = descriptor)
            {
                d.assignString(true, Array.getFirstElementAddress(string) + (offset << 1), length, handle);
                result = (int) MalikSystem.syscall((long) d.getDescriptorAddress(), 0x002c);
            }
            return result;
        }
        error = 0;
        synchronized(MONITOR)
        {
            label0:
            {
                if(!installed)
                {
                    error = 1;
                    result = 0;
                    break label0;
                }
                synchronized(d = descriptor)
                {
                    d.assignString(true, Array.getFirstElementAddress(string) + (offset << 1), length, handle);
                    result = (int) MalikSystem.syscall((long) d.getDescriptorAddress(), 0x002c);
                }
            }
        }
        if(error == 1)
        {
            throw new UninstalledFontException("SystemFont.substringWidth: этот шрифт был удалён из системы.");
        }
        return result;
    }

    public int getHeight() {
        return height;
    }

    public int getBaselineHeight() {
        return baselineHeight;
    }

    public int getBaselinePosition() {
        return baselinePosition;
    }

    public int getStyle() {
        return style;
    }

    public String getName() {
        return name;
    }

    int getHandle() {
        return handle;
    }

    SystemFont getNext() {
        return next;
    }
}
