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

public final class CloudFileSystem extends Object implements VirtualFileSystemReadOnly, VirtualFileSystemReadWrite
{
    public static final CloudFileSystem instance;

    static {
        instance = new CloudFileSystem();
    }

    static long computeFields(long time) {
        return CalendarSystem.gregorian.computeFields(time, 0);
    }

    static long computeTime(long fields) {
        return CalendarSystem.gregorian.computeTime(fields, 0);
    }

    private CloudFileSystem() {
    }

    public String toString() {
        return "Файловая система приложения";
    }

    public void readAttributes(String objectName, FileAttributes objectAttr) throws IOException {
        int len;
        char[] name;
        AttributesDescriptor d;
        if(objectName == null)
        {
            throw new NullPointerException("CloudFileSystem.readAttributes: аргумент objectName равен нулевой ссылке.");
        }
        d = new AttributesDescriptor();
        objectName.getChars(0, len = objectName.length(), name = new char[len + 1], 0);
        if(MalikSystem.syscall(Array.getFirstElementAddress(name), d.getDescriptorAddress(), 0x001c) == 0L)
        {
            throw new ObjectNotFoundException((new StringBuilder()).append("CloudFileSystem.readAttributes: объект ").append(objectName).append(" не найден.").toString(), objectName);
        }
        if(objectAttr != null) objectAttr.setAttributes(d.attributes, computeTime(d.timeOfCreation), computeTime(d.timeOfLastWrite), computeTime(d.timeOfLastAccess));
    }

    public int getObjectNameMaximumLength() throws IOException {
        return (int) MalikSystem.syscall(0L, 0x0016);
    }

    public FileEnumeration findFirst() throws IOException {
        int h;
        int len;
        char[] directory = new char[] { '/', '\0' };
        char[] name = new char[len = getObjectNameMaximumLength() + 1];
        FileInfoDescriptor d = new FileInfoDescriptor(len, Array.getFirstElementAddress(name));
        return (h = (int) MalikSystem.syscall(Array.getFirstElementAddress(directory), d.getDescriptorAddress(), 0x0018)) > 0 ? new CloudFileEnumeration(h, name, d) : null;
    }

    public FileEnumeration findFirst(String objectName) throws IOException {
        int h;
        int lim;
        int len;
        char[] name;
        char[] directory;
        FileInfoDescriptor d;
        if(objectName == null)
        {
            throw new NullPointerException("CloudFileSystem.findFirst: аргумент objectName равен нулевой ссылке.");
        }
        name = new char[len = getObjectNameMaximumLength() + 1];
        d = new FileInfoDescriptor(len, Array.getFirstElementAddress(name));
        if(!objectName.startsWith("/")) objectName = "/".concat(objectName);
        lim = (len = objectName.length()) - 1;
        if(len > 0 && objectName.charAt(lim) != '/')
        {
            objectName.getChars(0, len, directory = new char[len + 1], 0);
            return (h = (int) MalikSystem.syscall(Array.getFirstElementAddress(directory), d.getDescriptorAddress(), 0x0018)) > 0 ? new CloudFileEnumeration(h, name, d) : null;
        }
        if(len > 1)
        {
            objectName.getChars(0, lim, directory = new char[len], 0);
            if(MalikSystem.syscall(Array.getFirstElementAddress(directory), d.getDescriptorAddress(), 0x001c) == 0L || (d.attributes & FileAttributes.DIRECTORY) == 0)
            {
                throw new DirectoryNotFoundException((new StringBuilder()).append("CloudFileSystem.findFirst: папка ").append(objectName).append(" не найдена.").toString(), objectName);
            }
        }
        objectName.getChars(0, len, directory = new char[len + 1], 0);
        return (h = (int) MalikSystem.syscall(Array.getFirstElementAddress(directory), d.getDescriptorAddress(), 0x0018)) > 0 ? new CloudFileEnumeration(h, name, d) : null;
    }

    public InputStream openFileForRead(String fileName) throws IOException {
        int h;
        int len;
        char[] name;
        if(fileName == null)
        {
            throw new NullPointerException("CloudFileSystem.openFileForRead: аргумент fileName равен нулевой ссылке.");
        }
        if((len = fileName.length()) <= 0)
        {
            throw new IllegalArgumentException("CloudFileSystem.openFileForRead: аргумент fileName является пустой строкой.");
        }
        fileName.getChars(0, len, name = new char[len + 1], 0);
        if((h = (int) MalikSystem.syscall(Array.getFirstElementAddress(name), 0x01, 0x0010)) == 0)
        {
            throw new FileNotFoundException((new StringBuilder()).append("CloudFileSystem.openFileForRead: файл ").append(fileName).append(" не найден или занят.").toString(), fileName);
        }
        return new FileInputStream(fileName, h);
    }

    public void writeAttributes(String objectName, FileAttributes objectAttr) throws IOException {
        int len;
        char[] name;
        AttributesDescriptor d;
        if(objectName == null)
        {
            throw new NullPointerException("CloudFileSystem.writeAttributes: аргумент objectName равен нулевой ссылке.");
        }
        if(objectAttr == null)
        {
            throw new NullPointerException("CloudFileSystem.writeAttributes: аргумент objectAttr равен нулевой ссылке.");
        }
        objectName.getChars(0, len = objectName.length(), name = new char[len + 1], 0);
        (d = new AttributesDescriptor()).attributes = objectAttr.getAttributes() & ~FileAttributes.DIRECTORY;
        d.timeOfCreation = computeFields(objectAttr.getCreationTime());
        d.timeOfLastWrite = computeFields(objectAttr.getLastWriteTime());
        d.timeOfLastAccess = computeFields(objectAttr.getLastAccessTime());
        if(MalikSystem.syscall(Array.getFirstElementAddress(name), d.getDescriptorAddress(), 0x001d) == 0L)
        {
            throw new ObjectNotFoundException((new StringBuilder()).append("CloudFileSystem.writeAttributes: объект ").append(objectName).append(" не найден.").toString(), objectName);
        }
    }

    public void move(String oldObjectName, String newObjectName) throws IOException {
        int len;
        char[] oldName;
        char[] newName;
        if(oldObjectName == null)
        {
            throw new NullPointerException("CloudFileSystem.move: аргумент oldObjectName равен нулевой ссылке.");
        }
        if(newObjectName == null)
        {
            throw new NullPointerException("CloudFileSystem.move: аргумент newObjectName равен нулевой ссылке.");
        }
        oldObjectName.getChars(0, len = oldObjectName.length(), oldName = new char[len + 1], 0);
        newObjectName.getChars(0, len = newObjectName.length(), newName = new char[len + 1], 0);
        if(MalikSystem.syscall(Array.getFirstElementAddress(oldName), Array.getFirstElementAddress(newName), 0x001b) == 0L)
        {
            throw new MoveOperationException("CloudFileSystem.move: не удалось выполнить перемещение и/или переименование объекта.", oldObjectName, newObjectName);
        }
    }

    public void deleteDirectory(String directoryName) throws IOException {
        int len;
        char[] name;
        AttributesDescriptor d;
        if(directoryName == null)
        {
            throw new NullPointerException("CloudFileSystem.deleteDirectory: аргумент directoryName равен нулевой ссылке.");
        }
        d = new AttributesDescriptor();
        directoryName.getChars(0, len = directoryName.length(), name = new char[len + 1], 0);
        if(MalikSystem.syscall(Array.getFirstElementAddress(name), d.getDescriptorAddress(), 0x001c) == 0L || (d.attributes & FileAttributes.DIRECTORY) == 0)
        {
            throw new DirectoryNotFoundException((new StringBuilder()).append("CloudFileSystem.deleteDirectory: папка ").append(directoryName).append(" не найдена.").toString(), directoryName);
        }
        if(MalikSystem.syscall((long) Array.getFirstElementAddress(name), 0x001f) == 0L)
        {
            throw new DirectoryDeletionException(
                (new StringBuilder()).append("CloudFileSystem.deleteDirectory: не удалось удалить папку ").append(directoryName).append('.').toString(), directoryName
            );
        }
    }

    public void deleteFile(String fileName) throws IOException {
        int len;
        char[] name;
        AttributesDescriptor d;
        if(fileName == null)
        {
            throw new NullPointerException("CloudFileSystem.deleteFile: аргумент fileName равен нулевой ссылке.");
        }
        d = new AttributesDescriptor();
        fileName.getChars(0, len = fileName.length(), name = new char[len + 1], 0);
        if(MalikSystem.syscall(Array.getFirstElementAddress(name), d.getDescriptorAddress(), 0x001c) == 0L || (d.attributes & FileAttributes.DIRECTORY) != 0)
        {
            throw new FileNotFoundException((new StringBuilder()).append("CloudFileSystem.deleteFile: файл ").append(fileName).append(" не найден.").toString(), fileName);
        }
        if(MalikSystem.syscall((long) Array.getFirstElementAddress(name), 0x0017) == 0L)
        {
            throw new FileDeletionException((new StringBuilder()).append("CloudFileSystem.deleteFile: не удалось удалить файл ").append(fileName).append('.').toString(), fileName);
        }
    }

    public void createDirectory(String directoryName) throws IOException {
        int len;
        char[] name;
        if(directoryName == null)
        {
            throw new NullPointerException("CloudFileSystem.createDirectory: аргумент directoryName равен нулевой ссылке.");
        }
        directoryName.getChars(0, len = directoryName.length(), name = new char[len + 1], 0);
        if(MalikSystem.syscall((long) Array.getFirstElementAddress(name), 0x001e) == 0L)
        {
            throw new DirectoryCreationException(
                (new StringBuilder()).append("CloudFileSystem.createDirectory: не удалось создать папку ").append(directoryName).append('.').toString(), directoryName
            );
        }
    }

    public OutputStream createFile(String fileName) throws IOException {
        int h;
        int len;
        char[] name;
        if(fileName == null)
        {
            throw new NullPointerException("CloudFileSystem.createFile: аргумент fileName равен нулевой ссылке.");
        }
        if((len = fileName.length()) <= 0)
        {
            throw new IllegalArgumentException("CloudFileSystem.createFile: аргумент fileName является пустой строкой.");
        }
        fileName.getChars(0, len, name = new char[len + 1], 0);
        if((h = (int) MalikSystem.syscall(Array.getFirstElementAddress(name), 0x02, 0x0010)) == 0)
        {
            throw new FileCreationException((new StringBuilder()).append("CloudFileSystem.createFile: не удалось создать файл ").append(fileName).append('.').toString(), fileName);
        }
        return new FileOutputStream(fileName, h);
    }

    public OutputStream openFileForAppend(String fileName) throws IOException {
        int h;
        int len;
        char[] name;
        if(fileName == null)
        {
            throw new NullPointerException("CloudFileSystem.openFileForAppending: аргумент fileName равен нулевой ссылке.");
        }
        if((len = fileName.length()) <= 0)
        {
            throw new IllegalArgumentException("CloudFileSystem.openFileForAppending: аргумент fileName является пустой строкой.");
        }
        fileName.getChars(0, len, name = new char[len + 1], 0);
        if((h = (int) MalikSystem.syscall(Array.getFirstElementAddress(name), 0x00, 0x0010)) == 0)
        {
            throw new FileNotFoundException((new StringBuilder()).append("CloudFileSystem.openFileForAppending: файл ").append(fileName).append(" не найден или занят.").toString(), fileName);
        }
        return new FileOutputStream(fileName, h);
    }

    public IOStream openFileForReadWrite(String fileName) throws IOException {
        int h;
        int len;
        char[] name;
        if(fileName == null)
        {
            throw new NullPointerException("CloudFileSystem.openFileForReadWrite: аргумент fileName равен нулевой ссылке.");
        }
        if((len = fileName.length()) <= 0)
        {
            throw new IllegalArgumentException("CloudFileSystem.openFileForReadWrite: аргумент fileName является пустой строкой.");
        }
        fileName.getChars(0, len, name = new char[len + 1], 0);
        if((h = (int) MalikSystem.syscall(Array.getFirstElementAddress(name), 0x03, 0x0010)) == 0)
        {
            throw new FileNotFoundException((new StringBuilder()).append("CloudFileSystem.openFileForReadWrite: файл ").append(fileName).append(" не найден или занят.").toString(), fileName);
        }
        return new FileIOStream(fileName, h);
    }
}

class AttributesDescriptor extends SystemDescriptor
{
    /*
     * время в формате 0xггггммддччммсссс
     * биты 48-63: год (1-65535)
     * биты 40-47: месяц (1-12)
     * биты 32-39: число (1-31)
     * биты 24-31: час (0-23)
     * биты 16-23: минута (0-59)
     * биты 00-15: миллисекунда (0-59999)
     */
    public long timeOfCreation;
    public long timeOfLastAccess;
    public long timeOfLastWrite;
    public int attributes;
    public int reserved0;

    public AttributesDescriptor() {
    }
}

class FileInfoDescriptor extends AttributesDescriptor
{
    public long size;
    public int nameLength;
    public int nameAddress;

    public FileInfoDescriptor(int nameLength, int nameAddress) {
        this.nameLength = nameLength;
        this.nameAddress = nameAddress;
    }
}

final class CloudFileEnumeration extends FileEnumeration
{
    private int handle;
    private final char[] objectName;
    private final FileInfoDescriptor descriptor;

    public CloudFileEnumeration(int handle, char[] objectName, FileInfoDescriptor descriptor) {
        this.handle = handle;
        this.objectName = objectName;
        this.descriptor = descriptor;
        setAttributes(objectName, descriptor);
    }

    public void close() throws IOException {
        synchronized(descriptor)
        {
            closeHandle();
        }
    }

    public boolean findNext() throws IOException {
        boolean result;
        int error = 0;
        FileInfoDescriptor d;
        synchronized(d = descriptor)
        {
            label0:
            {
                int h;
                if((h = handle) == 0)
                {
                    error = 1;
                    result = false;
                    break label0;
                }
                if(MalikSystem.syscall(h, d.getDescriptorAddress(), 0x0019) != 1L)
                {
                    result = false;
                    break label0;
                }
                setAttributes(objectName, d);
                result = true;
            }
        }
        if(error == 1)
        {
            throw new ClosedEnumerationException("FileEnumeration.findNext: перечисление файлов закрыто.");
        }
        return result;
    }

    protected void $finalize$() {
        closeHandle();
    }

    private void closeHandle() {
        int h;
        if((h = handle) != 0)
        {
            MalikSystem.syscall((long) h, 0x001a);
            handle = 0;
        }
    }

    private void setAttributes(char[] f, FileInfoDescriptor d) {
        setAttributes(
            d.attributes, CloudFileSystem.computeTime(d.timeOfCreation), CloudFileSystem.computeTime(d.timeOfLastWrite), CloudFileSystem.computeTime(d.timeOfLastAccess), d.size,
            new String(f, 0, Array.findf(f, 0, 0))
        );
    }
}
