/*************************************************************************
           RIFFParser.h  -  parser for the RIFF format
                             -------------------
    begin                : Tue Mar 20 2002
    copyright            : (C) 2002 by Thomas Eschenbacher
    email                : Thomas.Eschenbacher@gmx.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 RIFF_PARSER_H
#define RIFF_PARSER_H

#include "config.h"

#include <QByteArray>
#include <QList>
#include <QObject>
#include <QStringList>

#include "libkwave/ByteOrder.h"

#include "RIFFChunk.h"

class QIODevice;

namespace Kwave
{
    class RIFFChunk;

    class RIFFParser: public QObject
    {
        Q_OBJECT
    public:

        /**
         * Constructor.
         * @param device QIODevice to parse through.
         * @param main_chunks list of known names of main chunks
         * @param known_subchunks list of known subchunks
         */
        RIFFParser(QIODevice &device, const QStringList &main_chunks,
                   const QStringList &known_subchunks);

        /** Destructor */
        virtual ~RIFFParser() override;

        /**
         * Parses the whole source.
         * @return true if passed without any error
         */
        bool parse();

        /**
         * Parses a range of the source and adds all found chunks to a
         * list of chunks. Also parses recursively for sub-chunks and
         * builds a tree.
         * @param parent pointer to the parent node
         * @param offset start of the chunk name in the source
         * @param length size of the area where to search
         * @return true if passed without any error
         */
        bool parse(Kwave::RIFFChunk *parent,
                   quint32 offset, quint32 length);

        /**
         * Returns true if the source contains no structural errors and
         * no garbage or empty chunks.
         */
        bool isSane();

        /**
         * Dumps the structure of all chunks, useful for debugging.
         */
        void dumpStructure();

        /**
         * Tries to find a chunk in the tree of parsed chunks, only
         * accepting a full match.
         * @param path the full path of the chunk to be searched
         * @return the found chunk or zero if nothing found
         */
        Kwave::RIFFChunk *findChunk(const QByteArray &path);

        /** Returns the number of times a chunk is present */
        unsigned int chunkCount(const QByteArray &path);

        /**
         * Tries very hard to find a missing chunk by stepping through
         * the whole file or source.
         * @param name the 4-byte name of the chunk
         * @return the found chunk or zero if nothing found
         */
        Kwave::RIFFChunk *findMissingChunk(const QByteArray &name);

        /**
         * Tries to repair the RIFF file by solving inconsistencies
         * @see collectGarbage()
         * @see joinGarbageToEmpty()
         * @see fixGarbageEnds()
         * @see discardGarbage()
         */
        void repair();

    public slots:

    /** Cancels the current action. */
    void cancel();

    signals:

        /** emits the name of the currently performed action */
        void action(const QString &name);

        /** emits a progress in percent */
        void progress(int percent);

    protected:

        /**
         * Reads a 4-byte string from the source device at a given
         * offset. The current source position will be set to 4 bytes
         * after the given offset afterwards.
         * @param offset position within the source, no range checks!
         * @return string with 4 bytes
         */
        QByteArray read4ByteString(qint64 offset);

        /**
         * Tries to find the chunk name in the list of known main
         * chunk names and returns RIFFChunk::Main if successful.
         * Otherwise returns RIFFChunk::Sub or RIFFChunk::Garbage
         * if the chunk name is not valid.
         * @param name the name of the chunk
         * @return RIFFChunk::Main or RIFFChunk::Sub or
         *         RIFFChunk::Garbage
         */
        Kwave::RIFFChunk::ChunkType guessType(const QByteArray &name);

        /** Returns true if the given chunk name is valid */
        bool isValidName(const char *name);

        /**
         * Returns true if the given chunk name is known as main or sub chunk
         */
        bool isKnownName(const QByteArray &name);

        /**
         * Tries to detect the endianness of the source. If successful, the
         * endianness will be set. If failed, will be set to "Unknown".
         */
        void detectEndianness();

        /**
         * Creates and adds a new chunk. Will be parented to the first
         * non-garbage chunk.
         * @param parent pointer to the parent node (or null if root node)
         * @param name the 4-byte name of the chunk
         * @param format the 4-byte format specifier, only valid for
         *               main chunks, contains invalid data in sub-chunks
         * @param length size of the chunk's data
         * @param phys_offset start of the chunk name in the source
         * @param phys_length length allocated in the source (file)
         * @param type chunk type, @see RIFFChunk::ChunkType
         * @return pointer to the new created chunk
         */
        Kwave::RIFFChunk *addChunk(Kwave::RIFFChunk *parent,
                                   const QByteArray &name,
                                   const QByteArray &format,
                                   quint32 length,
                                   quint32 phys_offset,
                                   quint32 phys_length,
                                   Kwave::RIFFChunk::ChunkType type);

        /**
         * Adds a chunk that has no valid name and thus is not recognized.
         * It is assumed that it only contains undecodeable garbage without
         * any valid name, length or other header information.
         * @param parent pointer to the parent node
         * @param offset start of the chunk name in the source
         * @param length length of the garbage area in bytes
         * @return true if creation succeeded, false if out of memory
         */
        bool addGarbageChunk(Kwave::RIFFChunk *parent, quint32 offset,
                             quint32 length);

        /**
         * Adds a chunk with a valid name and no length information.
         * @param parent pointer to the parent node
         * @param name the valid name of the chunk
         * @param offset start of the chunk name in the source
         * @return true if creation succeeded, false if out of memory
         */
        bool addEmptyChunk(Kwave::RIFFChunk *parent, const QByteArray &name,
                           quint32 offset);

        /**
         * Recursively creates a "flat" list of all chunks.
         * @param parent node to start with
         * @param list receives references to the chunks
         */
        void listAllChunks(Kwave::RIFFChunk &parent,
                           Kwave::RIFFChunkList &list);

        /**
         * Returns a pointer to a chunk that starts at a given offset
         * or zero if none found.
         * @param offset the start position (physical start)
         * @return pointer to the chunk or zero
         */
        Kwave::RIFFChunk *chunkAt(quint32 offset);

        /**
         * Performs a scan for a 4-character chunk name over a range of the
         * source. Also emits progress bar info from
         * [(start/count) ... ((start+1)/count-1)]
         * @param name the name of the chunk to be found
         * @param offset position for start of the scan
         * @param length number of bytes to scan
         * @param progress_start start of the progress [0..progress_count-1]
         * @param progress_count number of progress sections
         * @return list of positions of where the name exists
         */
        QList<quint32> scanForName(const QByteArray &name, quint32 offset,
                                   quint32 length,
                                   int progress_start = 0,
                                   int progress_count = 1);

    private:

        /**
         * clear all main chunks that contain only garbage and
         * convert them into garbage chunks
         */
        void collectGarbage();

        /**
         * joins garbage to previous empty chunks. If a chunk follows a
         * previously empty chunk with an unknown name it will be joined too.
         * @return true if it needs an additional pass
         */
        bool joinGarbageToEmpty();

        /** fixes the end of garbage chunks to no longer overlap valid chunks */
        void fixGarbageEnds();

        /** Discards all garbage sub-chunks */
        void discardGarbage(Kwave::RIFFChunk &chunk);

        /** I/O device with the source of the file */
        QIODevice &m_dev;

        /** root chunk of the source */
        Kwave::RIFFChunk m_root;

        /** list of known names of main chunks */
        QStringList m_main_chunk_names;

        /** list of known names of sub chunks */
        QStringList m_sub_chunk_names;

        /** endianness of the RIFF file, auto-detected */
        Kwave::byte_order_t m_endianness;

        /** can be set to true in order to cancel a running operation */
        bool m_cancel;

    };
}

#endif /* RIFF_PARSER_H */

//***************************************************************************
//***************************************************************************
