/*
    Реализация спецификаций 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 HandleOutputStream extends OutputStream
{
    private static final int NO_ERROR = 0;
    private static final int FILE_NOT_FOUND = 1;
    private static final int FILE_UNABLE_TO_CREATE = 2;
    private static final int FILE_NAME_NOT_SPECIFIED = 3;
    private static final int BUFFER_SIZE = 1 << 9; /* должен быть целой степенью двойки */

    private final int openError;
    private final int address;
    int handle;
    int position;
    private final byte[] buffer;
    private final String fileName;
    final DataDescriptor descriptor;

    HandleOutputStream(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 = access == 0 || access == 3 ? FILE_NOT_FOUND : FILE_UNABLE_TO_CREATE;
            } else
            {
                e = NO_ERROR;
            }
        }
        if(h == 0)
        {
            this.openError = e;
            this.address = 0;
            this.buffer = null;
            this.fileName = fileName;
            this.descriptor = null;
            return;
        }
        buffer = new byte[BUFFER_SIZE];
        this.openError = NO_ERROR;
        this.address = Array.getFirstElementAddress(buffer);
        this.handle = h;
        this.buffer = buffer;
        this.fileName = fileName;
        this.descriptor = new DataDescriptor();
    }

    HandleOutputStream(String fileName, DataDescriptor descriptor, int handle) {
        byte[] buffer;
        if(descriptor == null) descriptor = new DataDescriptor();
        buffer = new byte[BUFFER_SIZE];
        this.openError = NO_ERROR;
        this.address = Array.getFirstElementAddress(buffer);
        this.handle = handle;
        this.buffer = buffer;
        this.fileName = fileName;
        this.descriptor = descriptor;
    }

    public void flush() throws IOException {
        int error;
        checkOpenError();
        error = 0;
        synchronized(descriptor)
        {
            label0:
            {
                int bufferPosition;
                if(handle == 0)
                {
                    error = 1;
                    break label0;
                }
                if((bufferPosition = position) > 0)
                {
                    position = 0;
                    if(!writeFile(bufferPosition))
                    {
                        error = 2;
                        break label0;
                    }
                }
            }
        }
        switch(error)
        {
        case 1:
            throw new ClosedFileException("HandleOutputStream.flush: файловый поток вывода закрыт.");
        case 2:
            throw new IOException("HandleOutputStream.flush: ошибка произошла при записи файла.");
        }
    }

    public void write(int byteData) throws IOException {
        int error;
        checkOpenError();
        error = 0;
        synchronized(descriptor)
        {
            label0:
            {
                int bufferPosition;
                if(handle == 0)
                {
                    error = 1;
                    break label0;
                }
                buffer[bufferPosition = position] = (byte) byteData;
                if((position = ++bufferPosition) == BUFFER_SIZE)
                {
                    position = 0;
                    if(!writeFile(BUFFER_SIZE))
                    {
                        error = 2;
                        break label0;
                    }
                }
            }
        }
        switch(error)
        {
        case 1:
            throw new ClosedFileException("HandleOutputStream.write: файловый поток вывода закрыт.");
        case 2:
            throw new IOException("HandleOutputStream.write: ошибка произошла при записи файла.");
        }
    }

    public void write(byte[] src, int offset, int length) throws IOException {
        int error;
        if(src == null)
        {
            throw new NullPointerException("HandleOutputStream.write: аргумент src равен нулевой ссылке.");
        }
        Array.checkBound("HandleOutputStream.write", src.length, offset, length);
        checkOpenError();
        error = 0;
        synchronized(descriptor)
        {
            label0:
            {
                int bufferPosition;
                byte[] bufferContent;
                if(handle == 0)
                {
                    error = 1;
                    break label0;
                }
                bufferContent = buffer;
                bufferPosition = position;
                for(int writed; length > 0; offset += writed, length -= writed)
                {
                    int bufferRemainder = BUFFER_SIZE - bufferPosition;
                    writed = bufferRemainder <= length ? bufferRemainder : length;
                    Array.copy(src, offset, bufferContent, bufferPosition, writed);
                    if((bufferPosition += writed) == BUFFER_SIZE)
                    {
                        bufferPosition = 0;
                        if(!writeFile(BUFFER_SIZE))
                        {
                            error = 2;
                            position = 0;
                            break label0;
                        }
                    }
                }
                position = bufferPosition;
            }
        }
        switch(error)
        {
        case 1:
            throw new ClosedFileException("HandleOutputStream.write: файловый поток вывода закрыт.");
        case 2:
            throw new IOException("HandleOutputStream.write: ошибка произошла при записи файла.");
        }
    }

    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("HandleOutputStream.checkOpenError: файл ").append(name).append(" не найден или занят.").toString(), name);
        case FILE_UNABLE_TO_CREATE:
            throw new FileCreationException((new StringBuilder()).append("HandleOutputStream.checkOpenError: не удалось создать файл ").append(name).append('.').toString(), name);
        case FILE_NAME_NOT_SPECIFIED:
            throw new IOException("HandleOutputStream.checkOpenError: имя файла не было задано при создании этого экземпляра HandleOutputStream.");
        }
    }

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

    public final String getFileName() {
        return fileName;
    }

    final boolean writeFile(int length) {
        DataDescriptor descriptor;
        (descriptor = this.descriptor).setDataInfo(address, length);
        return (int) MalikSystem.syscall(handle, descriptor.getDescriptorAddress(), 0x0013) == length;
    }
}
