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

import java.io.*;
import malik.emulator.io.jar.*;
import malik.emulator.util.*;

public final class Class extends Object
{
    static final int PUBLIC = 0x0001;
    static final int PRIVATE = 0x0002;
    static final int PROTECTED = 0x0004;
    static final int STATIC = 0x0008;
    static final int FINAL = 0x0010;
    static final int SYNCHRONIZED = 0x0020;
    static final int VOLATILE = 0x0040;
    static final int TRANSIENT = 0x0080;
    static final int NATIVE = 0x0100;
    static final int INTERFACE = 0x0200;
    static final int ABSTRACT = 0x0400;
    static final int STRICTFP = 0x0800;
    static final int SYNTHETIC = 0x1000;
    static final int PRIMITIVE = 0x00010000;

    public static Class forName(String typeName) throws ClassNotFoundException {
        boolean array;
        int len;
        if(typeName == null)
        {
            throw new ClassNotFoundException("Class.forName: класс с именем-нулевой ссылкой не найден.");
        }
        if((len = typeName.length()) <= 0)
        {
            throw new ClassNotFoundException("Class.forName: класс с пустым именем не найден.");
        }
        if((array = typeName.charAt(0) == '[') && len == 2) switch(typeName.charAt(1))
        {
        default:
            break;
        case 'Z':
            return MalikSystem.getClassInstance("[Z");
        case 'C':
            return MalikSystem.getClassInstance("[C");
        case 'F':
            return MalikSystem.getClassInstance("[F");
        case 'D':
            return MalikSystem.getClassInstance("[D");
        case 'B':
            return MalikSystem.getClassInstance("[B");
        case 'S':
            return MalikSystem.getClassInstance("[S");
        case 'I':
            return MalikSystem.getClassInstance("[I");
        case 'J':
            return MalikSystem.getClassInstance("[J");
        }
        for(Class current = MalikSystem.getClassInstance("Ljava/lang/Object;"); current != null; current = current.nextType) if(typeName.equals(current.getName())) return current;
        if(array) for(int lim = len - 1, dimc = 1; dimc < 0x0100 && dimc < len; dimc++)
        {
            switch(typeName.charAt(dimc))
            {
            default:
                break;
            case 'Z':
                if(dimc == lim)
                {
                    return createArrayType(MalikSystem.getClassInstance("[Z"), dimc - 1);
                }
                break;
            case 'C':
                if(dimc == lim)
                {
                    return createArrayType(MalikSystem.getClassInstance("[C"), dimc - 1);
                }
                break;
            case 'F':
                if(dimc == lim)
                {
                    return createArrayType(MalikSystem.getClassInstance("[F"), dimc - 1);
                }
                break;
            case 'D':
                if(dimc == lim)
                {
                    return createArrayType(MalikSystem.getClassInstance("[D"), dimc - 1);
                }
                break;
            case 'B':
                if(dimc == lim)
                {
                    return createArrayType(MalikSystem.getClassInstance("[B"), dimc - 1);
                }
                break;
            case 'S':
                if(dimc == lim)
                {
                    return createArrayType(MalikSystem.getClassInstance("[S"), dimc - 1);
                }
                break;
            case 'I':
                if(dimc == lim)
                {
                    return createArrayType(MalikSystem.getClassInstance("[I"), dimc - 1);
                }
                break;
            case 'J':
                if(dimc == lim)
                {
                    return createArrayType(MalikSystem.getClassInstance("[J"), dimc - 1);
                }
                break;
            case 'L':
                if(typeName.charAt(lim) == ';' && lim - dimc > 1 && typeName.charAt(dimc + 1) != '[')
                {
                    return createArrayType(forName(typeName.substring(dimc + 1, lim)), dimc);
                }
                break;
            case '[':
                continue;
            }
            break;
        }
        throw new ClassNotFoundException((new StringBuilder()).append("Class.forName: класс ").append(typeName).append(" не найден.").toString());
    }

    private static Class findArrayType(Class componentType, int dimensionsMaximum) {
        int dimensionsResult = componentType.getArrayDimensionsQuantity();
        Class result = componentType;
        if(dimensionsResult > 0)
        {
            dimensionsMaximum += dimensionsResult;
            componentType = componentType.getCellType();
        }
        for(Class current = MalikSystem.getClassInstance("["); current != null; current = current.nextType)
        {
            int dimensionsCurrent;
            if(current.getCellType() == componentType && (dimensionsCurrent = current.getArrayDimensionsQuantity()) > dimensionsResult && dimensionsCurrent <= dimensionsMaximum)
            {
                dimensionsResult = dimensionsCurrent;
                result = current;
            }
        }
        return result;
    }

    private static Class createArrayType(Class componentType, int dimensionsQuantity) {
        boolean status;
        Class result;
        status = MalikSystem.enterMonopolyAccess();
        try
        {
            result = findArrayType(componentType, dimensionsQuantity);
            dimensionsQuantity -= result.getArrayDimensionsQuantity() - componentType.getArrayDimensionsQuantity();
            for(; dimensionsQuantity-- > 0; result = new Class(result));
        }
        finally
        {
            MalikSystem.leaveMonopolyAccess(status);
        }
        return result;
    }

    /*
     * Нельзя менять порядок полей здесь, можно добавлять новые поля в конец списка.
     */
    final int modifiers;
    private final int instanceSize;
    private final int nameIndex;
    final int[] referenceOffsets;
    private final int[] virtualAddresses;
    private final long[] dynamicIndexesAndAddresses;
    private final InterfaceEntry[] interfaceEntries;
    final Class superType;
    final Class componentType;
    private Class nextType;
    Thread initializingThread;

    /*
     * Новые поля можно добавлять только после этого комментария. Поля типов
     * boolean, char, byte и short нельзя добавлять.
     */
    private String name;
    private String canonicalName;

    private Class(Class componentType) {
        int len;
        int[] thisVirtuals;
        int[] objectVirtuals;
        Class firstArray = MalikSystem.getClassInstance("[");
        Class object = MalikSystem.getClassInstance("Ljava/lang/Object;");
        this.modifiers = FINAL | componentType.modifiers & (PUBLIC | PROTECTED | PRIVATE);
        this.instanceSize = 0x10;
        this.nameIndex = -1;
        this.referenceOffsets = object.referenceOffsets;
        this.virtualAddresses = thisVirtuals = new int[len = (objectVirtuals = object.virtualAddresses).length];
        this.dynamicIndexesAndAddresses = object.dynamicIndexesAndAddresses;
        this.interfaceEntries = new InterfaceEntry[0];
        this.superType = object;
        this.componentType = componentType;
        this.nextType = firstArray.nextType;
        this.name = (
            componentType.isArray() ? (new StringBuilder()).append('[').append(componentType.getName()) : (new StringBuilder()).append("[L").append(componentType.getName()).append(';')
        ).toString();
        MalikSystem.arraycopyf_int(objectVirtuals, 1, thisVirtuals, 1, len - 1);
        firstArray.nextType = this;
    }

    public String toString() {
        return (modifiers & PRIMITIVE) != 0 ? getName() : ((modifiers & INTERFACE) != 0 ? "interface " : "class ").concat(getName());
    }

    public boolean isArray() {
        return componentType != null;
    }

    public boolean isInterface() {
        return (modifiers & INTERFACE) != 0;
    }

    public boolean isInstance(Object reference) {
        return reference != null && isAssignableFrom(reference.getClass());
    }

    public boolean isAssignableFrom(Class type) {
        int modifiers;
        int thisDimensions;
        int typeDimensions;
        if(type == null)
        {
            throw new NullPointerException("Class.isAssignableFrom: аргумент type равен нулевой ссылке.");
        }
        if(type == this)
        {
            return true;
        }
        if(((modifiers = type.modifiers) & PRIMITIVE) != 0)
        {
            return false;
        }
        if((modifiers & INTERFACE) != 0 || type.componentType == null)
        {
            return ((modifiers = this.modifiers) & INTERFACE) != 0 && type.isInterfaceImplements(this) || (modifiers & PRIMITIVE) == 0 && type.isInheritedFrom(this);
        }
        if((thisDimensions = this.getArrayDimensionsQuantity()) <= 0)
        {
            return this == MalikSystem.getClassInstance("Ljava/lang/Object;");
        }
        if((typeDimensions = type.getArrayDimensionsQuantity()) == thisDimensions)
        {
            return this.getCellType().isAssignableFrom(type.getCellType());
        }
        if(thisDimensions < typeDimensions)
        {
            return this.getCellType() == MalikSystem.getClassInstance("Ljava/lang/Object;");
        }
        return false;
    }

    public String getName() {
        String result;
        if((result = name) == null) result = name = StringPool.getString(nameIndex);
        return result;
    }

    public String getCanonicalName() {
        int len;
        String result;
        StringBuilder buffer;
        if((result = canonicalName) != null) return result;
        if((len = (buffer = new StringBuilder()).append(getName()).length()) > 0 && buffer.get(0) == '[')
        {
            int dimc;
            for(dimc = 1; dimc < len && buffer.get(dimc) == '['; dimc++);
            if(dimc < len)
            {
                buffer.delete(0, dimc);
                len -= dimc;
                switch(buffer.get(0))
                {
                default:
                    break;
                case 'Z':
                    buffer.delete(0).append("boolean");
                    break;
                case 'C':
                    buffer.delete(0).append("char");
                    break;
                case 'F':
                    buffer.delete(0).append("float");
                    break;
                case 'D':
                    buffer.delete(0).append("delete");
                    break;
                case 'B':
                    buffer.delete(0).append("byte");
                    break;
                case 'S':
                    buffer.delete(0).append("short");
                    break;
                case 'I':
                    buffer.delete(0).append("int");
                    break;
                case 'J':
                    buffer.delete(0).append("long");
                    break;
                case 'L':
                    buffer.delete(0);
                    if(--len > 0 && buffer.get(--len) == ';') buffer.delete(len);
                    break;
                }
                for(int i = dimc; i-- > 0; buffer.append("[]"));
            }
        }
        return canonicalName = buffer.toString().replace('$', '.');
    }

    public InputStream getResourceAsStream(String resourceName) {
        int i;
        String className;
        ResourceStream result;
        if(resourceName == null) return null;
        if(resourceName.length() > 0 && resourceName.charAt(0) == '/')
        {
            resourceName = resourceName.substring(1);
        }
        else if((i = (className = getName()).lastIndexOf('.')) >= 0)
        {
            resourceName = className.substring(0, i + 1).replace('.', '/').concat(resourceName);
        }
        return (result = new ResourceStream("/res/".concat(resourceName), "/".concat(resourceName))).hasOpenError() ? null : result;
    }

    public Object newInstance() throws InstantiationException, IllegalAccessException {
        Object result;
        MalikSystem.invokeDefaultConstructor(result = allocateInstance(true));
        return result;
    }

    boolean isInheritedFrom(Class type) {
        for(Class current = superType; current != null; current = current.superType) if(current == type) return true;
        return false;
    }

    boolean isInterfaceImplements(Class type) {
        Class current = this;
        do
        {
            InterfaceEntry[] entries;
            for(int i = (entries = current.interfaceEntries).length; i-- > 0; ) if(entries[i].implementedInterface == type) return true;
        } while((current = current.superType) != null);
        return false;
    }

    int getArrayDimensionsQuantity() {
        int result = 0;
        for(Class current = componentType; current != null; current = current.componentType) result++;
        return result;
    }

    int getInterfaceMethodEntryPoint(Class type, int offset) {
        return getInterfaceEntry(type).interfaceMethodTableAddress + offset;
    }

    Class getCellType() {
        Class result;
        if((result = componentType) != null) for(Class current; (current = result.componentType) != null; result = current);
        return result;
    }

    Object cast(Object reference) {
        Class type;
        if(reference != null && !isAssignableFrom(type = reference.getClass()))
        {
            throw new ClassCastException(
                (new StringBuilder()).append("Class.cast: нельзя привести экземпляр типа ").append(type.getCanonicalName()).append(" к типу ").append(this.getCanonicalName()).append('.').toString()
            );
        }
        return reference;
    }

    Object allocateInstance() {
        Object result;
        try
        {
            result = allocateInstance(false);
        }
        catch(Exception e)
        {
            throw new InstantiationError(e.getMessage());
        }
        return result;
    }

    Object allocateInstance(boolean useDefaultConstructor) throws InstantiationException, IllegalAccessException {
        int modifiers;
        if(componentType != null)
        {
            throw new InstantiationException((new StringBuilder()).append("Class.newInstance: тип ").append(getCanonicalName()).append(" является массивом.").toString());
        }
        if(((modifiers = this.modifiers) & (ABSTRACT | INTERFACE | PRIMITIVE)) != 0)
        {
            if((modifiers & (ABSTRACT | INTERFACE)) != 0)
            {
                throw new InstantiationException((new StringBuilder()).append("Class.newInstance: тип ").append(getCanonicalName()).append(" является абстрактным.").toString());
            }
            if((modifiers & PRIMITIVE) != 0)
            {
                throw new InstantiationException((new StringBuilder()).append("Class.newInstance: тип ").append(getCanonicalName()).append(" является примитивным.").toString());
            }
        }
        if(useDefaultConstructor)
        {
            if((modifiers & PUBLIC) == 0)
            {
                throw new IllegalAccessException((new StringBuilder()).append("Class.newInstance: тип ").append(getCanonicalName()).append(" не является открытым (public).").toString());
            }
            if(virtualAddresses[0] == 0)
            {
                throw new IllegalAccessException(
                    (new StringBuilder()).append("Class.newInstance: класс ").append(getCanonicalName()).append(" не имеет открытого (public) конструктора без аргументов.").toString()
                );
            }
        }
        return Memory.allocateInstanceOf(this, instanceSize, -1);
    }

    private InterfaceEntry getInterfaceEntry(Class type) {
        Class current = this;
        do
        {
            InterfaceEntry[] entries;
            for(int i = (entries = current.interfaceEntries).length; i-- > 0; )
            {
                InterfaceEntry entry;
                if((entry = entries[i]).implementedInterface == type) return entry;
            }
        } while((current = current.superType) != null);
        return null;
    }
}

final class InterfaceEntry extends Object
{
    /* Нельзя менять порядок полей здесь, нельзя добавлять новые поля сюда. */
    final int interfaceMethodTableAddress;
    final Class implementedInterface;

    public InterfaceEntry(int interfaceMethodTableAddress, Class implementedInterface) {
        this.interfaceMethodTableAddress = interfaceMethodTableAddress;
        this.implementedInterface = implementedInterface;
    }
}
