// This file is part of GNOME Boxes. License: LGPLv2+

using Archive;

public errordomain Boxes.ArchiveError {
    UNIMPLEMENTED,
    FILE_NOT_FOUND,
    UNKNOWN_ARCHIVE_TYPE,
    INVALID_PARAMETER,
    INVALID_CONTEXT
}

public enum Boxes.ArchiveAccess {
    READ = 1,
    WRITE = 2,
    READWRITE = 3
}

/**
 * Provides a class to handle archives of any format.
 *
 * This is a wrapper for libarchive that makes our usecases a bit more abstract.
 * It also allows reading and writing from and to the same archive which libarchive doesnt.
 */
public class Boxes.Archivist : GLib.Object {
    private static const int BUFFER_SIZE = 10240;
    public string filename { get; protected set; }
    private Archive.Read? read_archive = null;
    private Archive.Write? write_archive = null;
    private ArchiveAccess access;

    public Archivist (string filename, ArchiveAccess access) throws Boxes.ArchiveError {
        this.filename = filename;
        this.access = access;

        if ( (!readable ()) && (!writable ()) )
            throw new Boxes.ArchiveError.INVALID_PARAMETER ("Invalid access parameter.");

        open_archive ();
    }

    ~Archivist () {
        debug ("Destroying archive '%s'.", filename);
        if (readable ())
            // if something goes wrong here we can't really do anything about it
            // examples from libarchive don't handle any failure here either
            read_archive.close ();

        if (writable ()) {
            try {
                flush.begin ();
                yield;
            } catch (Boxes.ArchiveError e) {
                /* Destructor is not allowed to throw exceptions
                 * Only possible exception here would be INVALID_CONTEXT
                 * but the context is valid since writable () */
            }

            // same as with read_archive.close ()
            write_archive.close ();
        }
    }

    public bool readable () {
        return (access & ArchiveAccess.READ) != 0;
    }
    public bool writable () {
        return (access & ArchiveAccess.WRITE) != 0;
    }

    // relative_src should be the source file relative to the archive root. E.g. /dir_in_archive/file.vala
    public async void extract_file (string relative_src, string dest) throws Boxes.ArchiveError {
        if (!readable ())
            throw new Boxes.ArchiveError.INVALID_CONTEXT ("Read access to this archive is not allowed.");

        debug ("Extracting file '%s' from archive '%s' to '%s'.", relative_src, filename, dest);
        throw new Boxes.ArchiveError.UNIMPLEMENTED ("This function is currently unimplemented.");
    }

    // relative_dest should be the destination file relative to the archive root. E.g. /dir_in_archive/file.vala
    public async void insert_file (string src, string relative_dest) throws Boxes.ArchiveError {
        if (!writable ())
            throw new Boxes.ArchiveError.INVALID_CONTEXT ("Write access to this archive is not allowed.");

        debug ("Inserting file '%s' into archive '%s'. Position in archive: '%s'.", src, filename, relative_dest);
        throw new Boxes.ArchiveError.UNIMPLEMENTED ("This function is currently unimplemented.");
    }

    // Flushes all changes for writable archives. This is also done automatically on destruction.
    public async void flush () throws Boxes.ArchiveError {
        if (!writable ())
            throw new Boxes.ArchiveError.INVALID_CONTEXT ("Write access to this archive is not allowed.");

        debug ("Flushing changes to archive '%s'.", filename);
        throw new Boxes.ArchiveError.UNIMPLEMENTED ("This function is currently unimplemented.");
    }

    private void open_archive () throws Boxes.ArchiveError {
        debug ("Opening archive '%s'.", filename);
        if (readable ()) {
            read_archive = new Archive.Read ();
            // let libarchive handle the compression and so on
            read_archive.support_compression_all ();
            read_archive.support_format_all ();
            if (read_archive.open_filename (filename, BUFFER_SIZE) != Archive.Result.OK)
                throw new Boxes.ArchiveError.UNKNOWN_ARCHIVE_TYPE ("Given filename is no supported archive.");
        }

        if (writable ()) {
            write_archive = new Archive.Write ();
            throw new Boxes.ArchiveError.UNIMPLEMENTED ("This function is currently unimplemented.");
        }
    }
}

