#include <sstream>
#include <cassert>

#include "geners/binaryIO.hh"
#include "geners/IOException.hh"

namespace gs {
    template <class Catalog>
    Catalog* readBinaryCatalog(std::istream& is, unsigned* compressionCode,
                               unsigned* mergeLevel,
                               std::vector<std::string>* annotations,
                               const bool allowReadingByDifferentClass,
                               const unsigned expectedformatId)
    {
        assert(compressionCode);
        assert(mergeLevel);
        assert(annotations);

        is.seekg(0, std::ios_base::beg);

        unsigned formatId = 0, endianness = 0;
        unsigned char sizelong = 0;
        read_pod(is, &formatId);
        read_pod(is, &endianness);
        read_pod(is, &sizelong);

        if (is.fail()) throw IOReadFailure(
            "In gs::readBinaryCatalog: input stream failure");

        if (endianness != 0x01020304 ||
              formatId != expectedformatId ||
              sizelong != sizeof(long))
            throw IOInvalidData("In gs::readBinaryCatalog: not \"geners\" "
                                "binary metafile or incompatible system "
                                "architecture");

        read_pod(is, compressionCode);
        read_pod(is, mergeLevel);
        read_pod_vector(is, annotations);
        ClassId id(is, 1);
        Catalog* readback = 0;

        ClassId catId(ClassId::makeId<Catalog>());
        if (id.name() == catId.name())
            // The reading is done by the same class as the writing.
            // Make sure the "read" function gets the correct class version.
            readback = Catalog::read(id, is);
        else
        {
            if (!allowReadingByDifferentClass)
            {
                std::ostringstream os;
                os << "In gs::readBinarCatalog: incompatible "
                   << "catalog class: written by \"" << id.name()
                   << "\", but reading is attempted by \""
                   << catId.name() << '"';
                throw IOInvalidData(os.str());
            }

            // The reading is not done by the same class as the writing.
            // All bets are off, and it is up to the user to decide whether
            // this makes sense. However, to maintain compatibility with
            // version 1 archives, we need to pass this version to the
            // catalog read function.
            if (id.version() == 1)
                catId.setVersion(1);

            readback = Catalog::read(catId, is);
        }

        // Catalogs do not necessarily know their size in advance,
        // so that they might read until the end of file is encountered.
        // However, the eof flag on the stream can result in various
        // problems later (for example, in writing to the stream).
        // Remove this flag.
        if (is.eof() && !is.fail() && !is.bad())
        {
            is.clear();
            is.seekg(0, std::ios_base::end);
        }

        return readback;
    }
}
