/*
 * Libarchive interface: header file

   Copyright (C) 2011 Lisa Vitolo <shainer@chakra-project.org>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.
*/

#ifndef ARCHIVE_H
#define ARCHIVE_H

#include <akabeicore_global.h>
#include <akabeioperation.h>
#include <libarchive++/archiveentry.h>
#include <libarchive++/archiveoperations.h>

#include <QStringList>
#include <QHash>

#include <exception>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <md5.h>

/* Forward declarations */
struct archive;
struct archive_entry;
class ArchiveEntry;

/**
 * @class ArchiveHandler
 * @brief handles the contents of an archive using libarchive.
 *
 * This class is not thread-safe.
 */
class AKABEICORESHARED_EXPORT ArchiveHandler
{
    QHash<QString, ArchiveEntry const*> entries;

    ArchiveHandler& operator=(ArchiveHandler const& other);

public:
    /**
     * Creates a new ArchiveHandler object from an archive, reading all the entries contained.
     * @param path the archive pathname.
     * @throw ArchiveException if something goes wrong while reading the archive.
     */
    ArchiveHandler(const QString &);
    virtual ~ArchiveHandler();
    
    QString filename() const;

    /**
     * Writes a new archive to disk
     * @param filename the path of the archive
     * @param files a \c QHash of files. The first value is the path inside the archive on disk, the second one the file on disk.
     * @returns true if successful
     */
    static bool write(const QString& filename, const QHash<QString, QString>& files);

    /**
     * @returns a list of the filenames contained in the archive.
     */
    QStringList getEntries() const;

    /**
     * @param fileName the file name.
     * @returns the size of an archive file.
     * @throw ArchiveException if the file doesn't exist.
     */
    size_t getEntrySize(QString const& fileName) const;

    /**
     * @returns the total size of all archive files.
     */
    size_t totalEntrySize() const;

    /**
     * Reads a text file.
     * @param textFile the archive file to read.
     * @returns a string with the text contained.
     * @throw ArchiveException if the files doesn't exist.
     */
    QString readTextFile(const QString&);

    /**
     * Extracts a single entry.
     * @param source the filename.
     * @param destName the name it will have once extracted.
     * @param overwrite overwrite if already existent.
     * @throw ArchiveException if the file isn't in the archive or the extraction goes wrong.
     * @Note if the file requested is a directory, this function extracts all its subtree too.
     * @Note if overwrite is false and the file already exists, nothing happens.
     */
    void extract(const QString& source, const QString& destName, bool overwrite);
    void extract(const ArchiveEntry &entry, const QString& destName, bool overwrite);

    /**
     * Extracts all entries.
     * @param prefix the prefix path they will assume.
     * @param overwrite overwrite if already present.
     */
    void extractAll(const QString&, bool overwrite);

    /**
     * Scans all the archive entries and calls a functor for each of them.
     * @param handler the functor.
     * @throw ArchiveException if something goes wrong in the functor code.
     */
    void handleEntries(Akabei::EntryHandlerFunctor& handler);//FIXME: EntryHandlerFunctor should move into this file and be exported?

    /**
     * Computes the MD5 hash of some archive entries.
     * @param files the files to be computed.
     * @returns a \c QByteArray list with their hash in the same order as the input.
     * @throw ArchiveException if at least one of the files doesn't exist or is a directory.
     */
    QList<QByteArray> md5(const QStringList&);

    /**
     * @see md5
     * Convenience method for when there's just one file to compute
     */
    QByteArray md5(const QString &);

    /**
     * @returns whether a package archive contains a PKGINFO.
     */
    bool checkPackageValidity();

    /**
     * @returns a list of errors occurred during execution
     */
    Akabei::Error::List errors();

    /**
     * @returns the archive structure.
     */
    struct archive *getArchive();

private:
    /* Disable default and copy constructors */
    ArchiveHandler(ArchiveHandler const&);

    /* All needed for a proper extraction */
    const QStringList listFilesFromDirectory(const QString &, const QStringList &);
    QString getPrefixFromDirPath(const QString&);
    bool checkOverwriting(const QString&, ArchiveEntry const&, bool);

    /* Reads chunks of data from the archive */
    QString readData();

    /* Error handling */
    void checkForErrors();
    void checkForErrors(int);

    struct archive *archive;
    QString archiveName;
    Akabei::Error::List errs;
};

/**
 * @class ArchiveException
 * @brief Exception for archive-related issues, with a descriptive message.
 */
class ArchiveException : public std::exception
{
    const char* message;

public:
    ArchiveException(const char *m)
        : message(m)
    {
    }

    virtual const char *what() const throw() override {
        return message;
    }
};

#endif
