/*
    Реализация спецификаций 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.application.*;
import malik.emulator.io.cloud.*;
import malik.emulator.util.*;

public class Throwable extends Object
{
    private static Thread DISABLE_STACK_TRACE_FOR;

    static {
        MalikInterrupt.register(0x00, new ArithmeticExceptionHandler());
        MalikInterrupt.register(0x01, new NullPointerExceptionHandler());
        MalikInterrupt.register(0x02, new MemoryFaultExceptionHandler());
        MalikInterrupt.register(0x03, new ExecutionExceptionHandler());
        MalikInterrupt.register(0x04, new OperandExceptionHandler());
        MalikInterrupt.register(0x05, new StackFaultExceptionHandler());
        MalikInterrupt.register(0x06, new StackOverflowExceptionHandler());
        MalikInterrupt.register(0x07, new UnknownOperationExceptionHandler());
        MalikInterrupt.register(0x08, new ArrayIndexOutOfBoundsExceptionHandler());
        MalikInterrupt.register(0x09, new UnhandledExceptionHandler());
        MalikInterrupt.register(0x20, new ThrowStatementInterruptHandler());
        MalikInterrupt.register(0x21, new IsEnabledInterruptHandler());
        MalikInterrupt.register(0x22, new SetEnabledInterruptHandler());
    }

    static void disableStackTrace() {
        DISABLE_STACK_TRACE_FOR = Thread.currentThread();
    }

    static void enableStackTrace() {
        DISABLE_STACK_TRACE_FOR = null;
    }

    /* Нельзя менять порядок полей здесь, можно добавлять новые поля в конец списка. */
    private int address;
    private final Object monitor;

    /* Новые поля можно добавлять только после этого комментария. */
    private final int ignore;
    private final StackTraceElement[] trace;
    private final String message;

    public Throwable() {
        this(null, false);
    }

    public Throwable(String message) {
        this(message, false);
    }

    Throwable(String message, boolean implicit) {
        int ignore;
        StackTraceElement[] trace;
        Class errorType;
        Class thisType;
        Thread current;
        if(
            (thisType = getClass()) != (errorType = MalikSystem.getClassInstance("Ljava/lang/Error;")) && !thisType.isInheritedFrom(errorType) &&
            ThrowableStackTrace.enabled() && (current = Thread.currentThread()) != DISABLE_STACK_TRACE_FOR
        )
        {
            ignore = getIgnore(trace = StackTracer.trace(current), implicit);
        } else
        {
            ignore = 0;
            trace = null;
        }
        this.monitor = new Object();
        this.ignore = ignore;
        this.trace = trace;
        this.message = message;
    }

    public String toString() {
        String msg = getMessage();
        String cls = getClass().getName();
        return msg == null ? cls : (new StringBuilder()).append(cls).append(": ").append(msg).toString();
    }

    public void printStackTrace() {
        System.err.print(stackTraceToString());
    }

    public String getMessage() {
        return message;
    }

    public final void printRealStackTrace() {
        System.err.print(stackTraceToString());
    }

    public final String getRealMessage() {
        return message;
    }

    final int dummyThrowable() {
        return address ^ monitor.hashCode();
    }

    private int getIgnore(StackTraceElement[] trace, boolean implicit) {
        int result = 0;
        int limit = trace.length - 1;
        Class currType = getClass();
        Class thisType = MalikSystem.getClassInstance("Ljava/lang/Throwable;");
        do
        {
            for(StackTraceElement element1, element2;
                result < limit && (element1 = trace[result]) != null && (element2 = trace[result + 1]) != null &&
                element1.getClassName().equals(element2.getClassName()) && "<init>".equals(element2.getMethodName())
            ; result++);
            if(currType == thisType || result == limit) break;
            result++;
        } while((currType = currType.superType) != null);
        for(int i = implicit && result < limit ? result += 2 : ++result; i-- > 0; trace[i] = null);
        return result;
    }

    private String stackTraceToString() {
        int thisIgnore = ignore;
        StackTraceElement[] thisTrace = trace;
        StringBuilder result = (new StringBuilder()).append(getClass().getName());
        String newline;
        String msg;
        if((msg = message) != null) result.append(": ").append(msg);
        if((newline = System.getProperty("line.separator")) == null) newline = "\n";
        result.append(newline);
        if(thisTrace != null)
        {
            int len = thisTrace.length;
            for(int i = thisIgnore; i < len; i++)
            {
                StackTraceElement element;
                if((element = thisTrace[i]) != null) result.append("    в ").append(element.toString()).append(newline);
            }
        }
        return result.toString();
    }
}

final class StackTracer extends Object
{
    private static final int EIP = 0x0000;
    private static final int ESP = 0x0100;

    private static boolean INITIALIZED;
    private static int ELEMENTS_COUNT;
    private static long ELEMENTS_OFFSET;
    private static long[] STRINGS_OFFSETS;
    private static String[] STRINGS;
    private static DataInputStream DATA_STREAM;
    private static FileInputStream FILE_STREAM;
    private static final Object MONITOR;

    static {
        INITIALIZED = false;
        MONITOR = new Object();
    }

    public static StackTraceElement[] trace(Thread thread) {
        StackTraceElement[] result;
        if(thread == null)
        {
            throw new NullPointerException("StackTracer.trace: аргумент thread равен нулевой ссылке.");
        }
        synchronized(MONITOR)
        {
            try
            {
                Throwable.disableStackTrace();
                try
                {
                    if(!INITIALIZED) initialize();
                    thread.enterStackTrace();
                    try
                    {
                        boolean anotherThread = true;
                        int index = 0;
                        int len = 0;
                        int id = thread.getID() & 0xff;
                        int a = (int) MalikSystem.syscall((long) (ESP | id), 0x0007);
                        int b = thread.getStackBottom();
                        if(thread == Thread.currentThread())
                        {
                            anotherThread = false;
                            len--;
                            a += 0x10;
                        }
                        for(int address = a; address < b; address += 0x10)
                        {
                            int stackItemKind;
                            if((stackItemKind = MalikSystem.getIntAt(address + 0x0c)) == MalikSystem.STACKITEM_RETURN || stackItemKind == MalikSystem.STACKITEM_IRETURN) len++;
                        }
                        result = new StackTraceElement[len];
                        if(anotherThread && index < len) result[index++] = getElement((int) MalikSystem.syscall((long) (EIP | id), 0x0007));
                        for(int address = a; address < b; address += 0x10)
                        {
                            int stackItemKind;
                            if((stackItemKind = MalikSystem.getIntAt(address + 0x0c)) == MalikSystem.STACKITEM_RETURN || stackItemKind == MalikSystem.STACKITEM_IRETURN)
                            {
                                result[index++] = getElement(MalikSystem.getIntAt(address) - 1);
                                if(index >= len) break;
                            }
                        }
                    }
                    finally
                    {
                        thread.leaveStackTrace();
                    }
                }
                finally
                {
                    Throwable.enableStackTrace();
                }
            }
            catch(IOException e)
            {
                result = null;
            }
        }
        return result;
    }

    private static void initialize() throws IOException {
        int i;
        int len;
        long offset;
        FILE_STREAM = new FileInputStream(Run.instance.getAppProperty("Programme-Executable").concat(".bindbg"));
        DATA_STREAM = new DataInputStream(FILE_STREAM);
        FILE_STREAM.mark(Integer.MAX_VALUE);
        STRINGS = new String[len = DATA_STREAM.readInt()];
        STRINGS_OFFSETS = new long[len];
        for(offset = 4L, i = 0; i < len; i++)
        {
            STRINGS_OFFSETS[i] = offset;
            offset += FILE_STREAM.skip(DATA_STREAM.readUnsignedShort()) + 2L;
        }
        ELEMENTS_OFFSET = offset;
        ELEMENTS_COUNT = FILE_STREAM.available() / 18;
        INITIALIZED = true;
    }

    private static void seek(long offset) throws IOException {
        FILE_STREAM.reset();
        FILE_STREAM.skip(offset);
    }

    private static int getElementAddress(int index) throws IOException {
        if(index < 0) return Integer.MIN_VALUE;
        if(index >= ELEMENTS_COUNT) return Integer.MAX_VALUE;
        seek(ELEMENTS_OFFSET + (long) index * 18L);
        return DATA_STREAM.readInt();
    }

    private static String getStringAt(int index) throws IOException {
        String result;
        if(index < 0 || index >= STRINGS.length) return "";
        if((result = STRINGS[index]) == null)
        {
            seek(STRINGS_OFFSETS[index]);
            result = STRINGS[index] = DATA_STREAM.readUTF();
        }
        return result;
    }

    private static StackTraceElement getElement(int address) throws IOException {
        int limit1 = 0;
        int limitGuess = ELEMENTS_COUNT >> 1;
        int limit2 = ELEMENTS_COUNT;
        int address1;
        int addressGuess;
        int address2;
        int lineNumber;
        int classNameIndex;
        int methodNameIndex;
        int sourceNameIndex;
        for(; ; )
        {
            address1 = getElementAddress(limit1);
            address2 = getElementAddress(limit2);
            addressGuess = getElementAddress(limitGuess);
            if(limitGuess - limit1 <= 1 && limit2 - limitGuess <= 1) break;
            if(address >= address1 && address < addressGuess)
            {
                limit2 = limitGuess;
                limitGuess = (limit1 + limit2) >> 1;
                continue;
            }
            if(address >= addressGuess && address < address2)
            {
                limit1 = limitGuess;
                limitGuess = (limit1 + limit2) >> 1;
                continue;
            }
            return null;
        }
        if(address >= addressGuess)
        {
            if(limitGuess < 0 || limitGuess >= ELEMENTS_COUNT) return null;
            seek(ELEMENTS_OFFSET + (long) limitGuess * 18L + 4L);
        }
        else if(address >= address1)
        {
            if(limit1 < 0 || limit1 >= ELEMENTS_COUNT) return null;
            seek(ELEMENTS_OFFSET + (long) limit1 * 18L + 4L);
        }
        else
        {
            return null;
        }
        classNameIndex = DATA_STREAM.readInt();
        methodNameIndex = DATA_STREAM.readInt();
        sourceNameIndex = DATA_STREAM.readInt();
        lineNumber = DATA_STREAM.readUnsignedShort();
        return new StackTraceElement(getStringAt(classNameIndex), getStringAt(methodNameIndex), getStringAt(sourceNameIndex), lineNumber);
    }

    private StackTracer() {
    }
}

abstract class InterruptHandler extends Object implements Interrupt
{
    protected InterruptHandler() {
    }
}

abstract class ExceptionHandler extends InterruptHandler
{
    protected ExceptionHandler() {
    }
}

final class ArithmeticExceptionHandler extends ExceptionHandler implements InterruptVoid
{
    public ArithmeticExceptionHandler() {
    }

    public void interrupt() throws Throwable {
        Exception exception = new ArithmeticException("Ошибка деления целого числа на ноль.", true);
        MalikSystem.runExcept(MalikSystem.getReturnAddress(), exception);
    }
}

final class NullPointerExceptionHandler extends ExceptionHandler implements InterruptVoid
{
    public NullPointerExceptionHandler() {
    }

    public void interrupt() throws Throwable {
        Exception exception = new NullPointerException("Нулевая ссылка не может быть разыменована.", true);
        MalikSystem.runExcept(MalikSystem.getReturnAddress(), exception);
    }
}

final class MemoryFaultExceptionHandler extends ExceptionHandler implements InterruptVoid
{
    public MemoryFaultExceptionHandler() {
    }

    public void interrupt() throws Throwable {
        Exception exception = new NullPointerException("Доступ к некоторой области памяти запрещён.", true);
        MalikSystem.runExcept(MalikSystem.getReturnAddress(), exception);
    }
}

final class ExecutionExceptionHandler extends ExceptionHandler implements InterruptVoid
{
    public ExecutionExceptionHandler() {
    }

    public void interrupt() throws Throwable {
        Error error = new VerifyError("Попытка выполнить код в недоступной области памяти.");
        MalikSystem.runExcept(MalikSystem.getReturnAddress(), error);
    }
}

final class OperandExceptionHandler extends ExceptionHandler implements InterruptVoid
{
    public OperandExceptionHandler() {
    }

    public void interrupt() throws Throwable {
        Error error = new VerifyError("Команда не может обработать данные другого типа.");
        MalikSystem.runExcept(MalikSystem.getReturnAddress(), error);
    }
}

final class StackFaultExceptionHandler extends ExceptionHandler implements InterruptVoid
{
    public StackFaultExceptionHandler() {
    }

    public void interrupt() throws Throwable {
        Error error = new VerifyError("Попытка доступа за пределы стака.");
        MalikSystem.runExcept(MalikSystem.getReturnAddress(), error);
    }
}

final class StackOverflowExceptionHandler extends ExceptionHandler implements InterruptVoid
{
    public StackOverflowExceptionHandler() {
    }

    public void interrupt() throws Throwable {
        Error error = new StackOverflowError("Стак вызовов переполнен.");
        MalikSystem.runExcept(MalikSystem.getReturnAddress(), error);
    }
}

final class UnknownOperationExceptionHandler extends ExceptionHandler implements InterruptVoid
{
    public UnknownOperationExceptionHandler() {
    }

    public void interrupt() throws Throwable {
        Error error = new VerifyError("Неизвестная операция.");
        MalikSystem.runExcept(MalikSystem.getReturnAddress(), error);
    }
}

final class ArrayIndexOutOfBoundsExceptionHandler extends ExceptionHandler implements InterruptInt
{
    public ArrayIndexOutOfBoundsExceptionHandler() {
    }

    public void interrupt(int faultedIndex) throws Throwable {
        Exception exception = new ArrayIndexOutOfBoundsException(Integer.toString(faultedIndex), true);
        MalikSystem.runExcept(MalikSystem.getReturnAddress(), exception);
    }
}

final class UnhandledExceptionHandler extends ExceptionHandler implements InterruptObject
{
    public UnhandledExceptionHandler() {
    }

    public void interrupt(Object unhandledThrowable) throws Throwable {
        /* НЕВОЗМОЖНО: исключения всё равно будут где-нибудь обрабатываться. */
    }
}

final class ThrowStatementInterruptHandler extends InterruptHandler implements InterruptObject
{
    public ThrowStatementInterruptHandler() {
    }

    public void interrupt(Object throwingObject) throws Throwable {
        Throwable throwable;
        if(throwingObject == null)
        {
            throwable = new NullPointerException("Команда throw: throw null заменено на throw new NullPointerException(…).", true);
        }
        else if(!(throwingObject instanceof Throwable))
        {
            throwable = new VerifyError("Объект исключения может быть только типа Throwable.");
        }
        else
        {
            throwable = (Throwable) throwingObject;
        }
        MalikSystem.runExcept(MalikSystem.getReturnAddress() - 1, throwable);
    }
}

final class IsEnabledInterruptHandler extends InterruptHandler implements InterruptInt
{
    public IsEnabledInterruptHandler() {
    }

    public void interrupt(int resultAddress) {
        MalikSystem.setByteAt(resultAddress, (byte) (MalikSystem.getByteAt(MalikSystem.getLocalVariableAddress(this) - 0x18) & 0x01));
    }
}

final class SetEnabledInterruptHandler extends InterruptHandler implements InterruptInt
{
    public SetEnabledInterruptHandler() {
    }

    public void interrupt(int enabled) {
        MalikSystem.setIntAt(MalikSystem.getLocalVariableAddress(this) - 0x18, enabled);
    }
}
