// -*- mode: c++ -*-
#ifndef __IMP_WAD__45443636
#define __IMP_WAD__45443636

#include <imp/Prelude>
#include <imp/util/Optional>

namespace imp {
  namespace gfx {
    class Image;
  }

  namespace wad {
    enum struct Section;
    class Lump;
    class LumpIterator;

    void init();

    bool mount(StringView path);

    void merge();

    bool have_lump(StringView name);

    Optional<Lump> find(StringView name);

    Optional<Lump> find(std::size_t index);

    Optional<Lump> find(Section section, std::size_t index);

    LumpIterator section(Section s);

    std::size_t section_size(Section s);

    class LumpHash {
        uint32 hash_ {};

    public:
        LumpHash() = default;

        LumpHash(LumpHash&&) = default;

        LumpHash(const LumpHash&) = default;

        LumpHash(StringView str):
            hash_(1315423911)
        {
            for (int i = 0; i < 8 && str[i]; ++i) {
                int c = str[i];
                hash_ ^= (hash_ << 5) + toupper(c) + (hash_ >> 2);
            }
            hash_ &= 0xffff;
        }

        LumpHash(uint32 hash):
            hash_(hash) {}

        uint32 get() const
        { return hash_; }

        LumpHash& operator=(LumpHash&&) = default;

        LumpHash& operator=(const LumpHash&) = default;

        bool operator<(LumpHash other) const
        { return hash_ < other.hash_; }
    };

    enum struct Section {
        normal,
        textures,
        graphics,
        sprites,
        sounds
    };

    constexpr size_t num_sections = 5;

    class BasicLump {
        std::size_t id_;

    public:
        BasicLump(std::size_t id):
            id_(id) {}

        virtual ~BasicLump() {}

        StringView lump_name() const;

        std::size_t lump_index() const;

        Section section() const;

        std::size_t section_index() const;

        virtual std::istream& stream() = 0;

        virtual String as_bytes();

        virtual gfx::Image as_image();
    };

    class Lump {
        UniquePtr<BasicLump> data_ {};

    public:
        Lump() = default;

        Lump(UniquePtr<BasicLump> data):
            data_(std::move(data)) {}

        Lump(Lump&&) = default;

        Lump(const Lump&) = delete;

        Lump& operator=(Lump&&) = default;

        Lump& operator=(const Lump&) = delete;

        StringView lump_name() const
        { return data_->lump_name(); }

        std::size_t lump_index() const
        { return data_->lump_index(); }

        Section section() const
        { return data_->section(); }

        std::size_t section_index() const
        { return data_->section_index(); }

        std::istream& stream()
        { return data_->stream(); }

        String as_bytes()
        { return data_->as_bytes(); }

        char* bytes_ptr()
        {
            auto bytes = as_bytes();
            char *memory = new char[bytes.size()];
            std::copy(bytes.begin(), bytes.end(), memory);
            return memory;
        }

        gfx::Image as_image();
    };

    class LumpIterator {
        std::size_t index_ {};
        Section section_ {};
        Lump lump_;

    public:
        LumpIterator(Section section);

        Lump& operator*();

        Lump* operator->()
        { return &**this; }

        bool has_next() const;

        void next();

        operator bool() const
        { return has_next(); }

        LumpIterator& operator++()
        {
            next();
            return *this;
        }
    };
  }

  inline StringView to_string(wad::Section s)
  {
      using Section = wad::Section;
      switch (s) {
      case Section::normal:
          return "normal"_sv;

      case Section::textures:
          return "textures"_sv;

      case Section::graphics:
          return "graphics"_sv;

      case Section::sprites:
          return "sprites"_sv;

      case Section::sounds:
          return "sounds"_sv;
      }
  }
}

#endif //__IMP_WAD__45443636
