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

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

public abstract class HandleInputStream extends InputStream
{
    private static final int NO_ERROR = 0;
    private static final int FILE_NOT_FOUND = 1;
    private static final int FILE_NAME_NOT_SPECIFIED = 3;
    private static final int BUFFER_SIZE = 1 << 9; /* должен быть целой степенью двойки */
    private static final long BUFFER_POSITION_MASK = (long) (BUFFER_SIZE - 1);

    private final boolean shared;
    private final int openError;
    private final int address;
    int handle;
    private long markedPosition;
    private long currentPosition;
    private final byte[] markedBuffer;
    private final byte[] currentBuffer;
    private final String fileName;
    final DataDescriptor descriptor;

    HandleInputStream(String fileName, int access) {
        int h;
        int e;
        int len;
        byte[] buffer;
        if(fileName == null || (len = fileName.length()) <= 0)
        {
            h = 0;
            e = FILE_NAME_NOT_SPECIFIED;
        } else
        {
            char[] name;
            fileName.getChars(0, len, name = new char[len + 1], 0);
            if((h = (int) MalikSystem.syscall(Array.getFirstElementAddress(name), access, 0x0010)) == 0)
            {
                e = FILE_NOT_FOUND;
            } else
            {
                e = NO_ERROR;
            }
        }
        if(h == 0)
        {
            this.shared = false;
            this.openError = e;
            this.address = 0;
            this.markedBuffer = null;
            this.currentBuffer = null;
            this.descriptor = null;
            this.fileName = fileName;
            return;
        }
        buffer = new byte[BUFFER_SIZE];
        this.shared = false;
        this.openError = NO_ERROR;
        this.address = Array.getFirstElementAddress(buffer);
        this.handle = h;
        this.markedPosition = -1L;
        this.currentPosition = -1L;
        this.markedBuffer = new byte[BUFFER_SIZE];
        this.currentBuffer = buffer;
        this.fileName = fileName;
        this.descriptor = new DataDescriptor();
    }

    HandleInputStream(String fileName, DataDescriptor descriptor, int handle) {
        boolean shared;
        byte[] buffer;
        if(!(shared = descriptor != null)) descriptor = new DataDescriptor();
        buffer = new byte[BUFFER_SIZE];
        this.shared = shared;
        this.openError = NO_ERROR;
        this.address = Array.getFirstElementAddress(buffer);
        this.handle = handle;
        this.markedPosition = -1L;
        this.currentPosition = -1L;
        this.markedBuffer = new byte[BUFFER_SIZE];
        this.currentBuffer = buffer;
        this.fileName = fileName;
        this.descriptor = descriptor;
    }

    public abstract long getFileSize();

    public void reset() throws IOException {
        int error;
        checkOpenError();
        error = 0;
        synchronized(descriptor)
        {
            label0:
            {
                if(handle == 0)
                {
                    error = 1;
                    break label0;
                }
                currentPosition = markedPosition;
                Array.copy(markedBuffer, 0, currentBuffer, 0, BUFFER_SIZE);
            }
        }
        if(error == 1)
        {
            throw new ClosedFileException("HandleInputStream.reset: файловый поток ввода закрыт.");
        }
    }

    public void mark(int readLimit) {
        if(hasOpenError()) return;
        synchronized(descriptor)
        {
            if(handle != 0)
            {
                markedPosition = currentPosition;
                Array.copy(currentBuffer, 0, markedBuffer, 0, BUFFER_SIZE);
            }
        }
    }

    public boolean markSupported() {
        return true;
    }

    public int available() throws IOException {
        int error;
        long result;
        checkOpenError();
        error = 0;
        synchronized(descriptor)
        {
            label0:
            {
                long filePosition;
                if(handle == 0)
                {
                    error = 1;
                    result = 0L;
                    break label0;
                }
                filePosition = currentPosition;
                result = getFileSize() - (filePosition >= 0L ? filePosition : 0L);
            }
        }
        if(error == 1)
        {
            throw new ClosedFileException("HandleInputStream.available: файловый поток ввода закрыт.");
        }
        return (long) Integer.MAX_VALUE < result ? Integer.MAX_VALUE : (int) result;
    }

    public int read() throws IOException {
        int error;
        int result;
        checkOpenError();
        error = 0;
        synchronized(descriptor)
        {
            label0:
            {
                long filePosition;
                if(handle == 0)
                {
                    error = 1;
                    result = 0;
                    break label0;
                }
                if((filePosition = currentPosition) < 0L && !readFile(filePosition = 0L))
                {
                    error = 2;
                    result = 0;
                    break label0;
                }
                if(filePosition >= getFileSize())
                {
                    result = -1;
                    currentPosition = filePosition;
                    break label0;
                }
                result = currentBuffer[(int) (filePosition++ & BUFFER_POSITION_MASK)] & 0xff;
                currentPosition = filePosition;
                if((filePosition & BUFFER_POSITION_MASK) == 0L && !readFile(filePosition))
                {
                    error = 2;
                    result = 0;
                    break label0;
                }
            }
        }
        switch(error)
        {
        case 1:
            throw new ClosedFileException("HandleInputStream.read: файловый поток ввода закрыт.");
        case 2:
            throw new IOException("HandleInputStream.read: ошибка произошла при чтении файла.");
        }
        return result;
    }

    public int read(byte[] dst, int offset, int length) throws IOException {
        int error;
        int result;
        if(dst == null)
        {
            throw new NullPointerException("HandleInputStream.read: аргумент dst равен нулевой ссылке.");
        }
        Array.checkBound("HandleInputStream.read", dst.length, offset, length);
        checkOpenError();
        error = 0;
        synchronized(descriptor)
        {
            label0:
            {
                long fileSize;
                long filePosition;
                long fileRemainder;
                byte[] bufferContent;
                if(handle == 0)
                {
                    error = 1;
                    result = 0;
                    break label0;
                }
                if((filePosition = currentPosition) < 0L && !readFile(filePosition = 0L))
                {
                    error = 2;
                    result = 0;
                    break label0;
                }
                if(length <= 0)
                {
                    result = 0;
                    break label0;
                }
                if(filePosition >= (fileSize = getFileSize()))
                {
                    result = -1;
                    currentPosition = filePosition;
                    break label0;
                }
                bufferContent = currentBuffer;
                fileRemainder = fileSize - filePosition;
                result = length = (long) length <= fileRemainder ? length : (int) fileRemainder;
                currentPosition = filePosition + result;
                for(int readed; length > 0; offset += readed, length -= readed)
                {
                    int bufferPosition;
                    int bufferRemainder = BUFFER_SIZE - (bufferPosition = (int) (filePosition & BUFFER_POSITION_MASK));
                    long readedAsLong = (long) (readed = bufferRemainder <= length ? bufferRemainder : length);
                    Array.copy(bufferContent, bufferPosition, dst, offset, readed);
                    if(((filePosition += readedAsLong) & BUFFER_POSITION_MASK) == 0L && !readFile(filePosition))
                    {
                        error = 2;
                        currentPosition = filePosition;
                        break label0;
                    }
                }
            }
        }
        switch(error)
        {
        case 1:
            throw new ClosedFileException("HandleInputStream.read: файловый поток ввода закрыт.");
        case 2:
            throw new IOException("HandleInputStream.read: ошибка произошла при чтении файла.");
        }
        return result;
    }

    public long skip(long quantity) throws IOException {
        int error;
        long result;
        if(quantity < 0L) quantity = 0L;
        checkOpenError();
        error = 0;
        synchronized(descriptor)
        {
            label0:
            {
                long fileSize;
                long filePosition;
                long fileRemainder;
                long newPosition;
                if(handle == 0)
                {
                    error = 1;
                    result = 0L;
                    break label0;
                }
                if((filePosition = currentPosition) < 0L && !readFile(filePosition = 0L))
                {
                    error = 2;
                    result = 0L;
                    break label0;
                }
                if(filePosition >= (fileSize = getFileSize()))
                {
                    result = 0L;
                    currentPosition = filePosition;
                    break label0;
                }
                fileRemainder = fileSize - filePosition;
                result = quantity <= fileRemainder ? quantity : fileRemainder;
                currentPosition = newPosition = filePosition + result;
                if((filePosition & ~BUFFER_POSITION_MASK) != (newPosition &= ~BUFFER_POSITION_MASK) && !readFile(newPosition))
                {
                    error = 2;
                    break label0;
                }
            }
        }
        switch(error)
        {
        case 1:
            throw new ClosedFileException("HandleInputStream.skip: файловый поток ввода закрыт.");
        case 2:
            throw new IOException("HandleInputStream.skip: ошибка произошла при чтении файла.");
        }
        return result;
    }

    public String toString() {
        String name = fileName;
        return (handle != 0 ? "Открытый для чтения файл " : "Закрытый файл ").concat(name != null && name.length() > 0 ? name : "<имя не задано>");
    }

    public void checkOpenError() throws IOException {
        String name = fileName;
        switch(openError)
        {
        case FILE_NOT_FOUND:
            throw new FileNotFoundException((new StringBuilder()).append("HandleInputStream.checkOpenError: файл ").append(name).append(" не найден или занят.").toString(), name);
        case FILE_NAME_NOT_SPECIFIED:
            throw new IOException("HandleInputStream.checkOpenError: имя файла не было задано при создании этого экземпляра HandleInputStream.");
        }
    }

    public boolean hasOpenError() {
        int e;
        return (e = openError) > NO_ERROR && e <= FILE_NAME_NOT_SPECIFIED;
    }

    public final String getFileName() {
        return fileName;
    }

    final void resetPosition() {
        currentPosition = -1L;
    }

    private long seek(long delta) {
        return MalikSystem.syscall(handle, MalikSystem.getLocalVariableAddress(delta), 0x0014);
    }

    private boolean readFile(long position) {
        boolean result;
        int mustRead;
        long oldPosition = seek(0L);
        long fileRemainder = getFileSize() - position;
        DataDescriptor descriptor = this.descriptor;
        mustRead = (long) BUFFER_SIZE <= fileRemainder ? BUFFER_SIZE : (int) fileRemainder;
        seek(position - oldPosition);
        descriptor.setDataInfo(address, BUFFER_SIZE);
        result = (int) MalikSystem.syscall(handle, descriptor.getDescriptorAddress(), 0x0012) == mustRead;
        if(shared) seek(oldPosition - seek(0L));
        return result;
    }
}
