/*  Copyright (C) 1996-1997  Id Software, Inc.

    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.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

    See file, 'COPYING', for details.
*/

#include <common/bspfile.hh>
#include <common/cmdlib.hh>

// dmodelh2_t

void dmodelh2_t::stream_write(std::ostream &s) const
{
    s <= std::tie(mins, maxs, origin, headnode, visleafs, firstface, numfaces);
}

void dmodelh2_t::stream_read(std::istream &s)
{
    s >= std::tie(mins, maxs, origin, headnode, visleafs, firstface, numfaces);
}

// mvis_t

size_t mvis_t::header_offset() const
{
    return sizeof(int32_t) + (sizeof(int32_t) * bit_offsets.size() * 2);
}

void mvis_t::set_bit_offset(vistype_t type, size_t cluster, size_t offset)
{
    bit_offsets[cluster][type] = offset + header_offset();
}

int32_t mvis_t::get_bit_offset(vistype_t type, size_t cluster) const
{
    return bit_offsets[cluster][type] - header_offset();
}

void mvis_t::resize(size_t numclusters)
{
    bit_offsets.resize(numclusters);
}

void mvis_t::stream_read(std::istream &stream, const lump_t &lump)
{
    int32_t numclusters;

    stream >= numclusters;

    resize(numclusters);

    // read cluster -> offset tables
    for (auto &bit_offset : bit_offsets)
        stream >= bit_offset;

    // pull in final bit set
    auto remaining = lump.filelen - (static_cast<int32_t>(stream.tellg()) - lump.fileofs);
    bits.resize(remaining);
    stream.read(reinterpret_cast<char *>(bits.data()), remaining);
}

void mvis_t::stream_write(std::ostream &stream) const
{
    // no vis data
    if (!bit_offsets.size()) {
        return;
    }

    stream <= static_cast<int32_t>(bit_offsets.size());

    // write cluster -> offset tables
    for (auto &bit_offset : bit_offsets)
        stream <= bit_offset;

    // write bitset
    stream.write(reinterpret_cast<const char *>(bits.data()), bits.size());
}

// dmiptex_t

std::string dmiptex_t::name_as_string() const
{
    for (int i = 0; i < name.size(); ++i) {
        if (!name[i]) {
            return std::string(name.data(), name.data() + i);
        }
    }
    // unterminated
    return std::string(name.data(), name.size());
}

void dmiptex_t::stream_write(std::ostream &s) const
{
    s <= std::tie(name, width, height, offsets);
}

void dmiptex_t::stream_read(std::istream &s)
{
    s >= std::tie(name, width, height, offsets);
}

// miptex_t

size_t miptex_t::stream_size() const
{
    return data.size();
}

void miptex_t::stream_read(std::istream &stream, size_t len)
{
    data.resize(len);
    stream.read(reinterpret_cast<char *>(data.data()), len);

    reload_header();
}

void miptex_t::reload_header()
{
    imemstream miptex_stream(data.data(), data.size());

    dmiptex_t dtex;
    miptex_stream >= dtex;

    name = dtex.name.data();
    width = dtex.width;
    height = dtex.height;
    offsets = dtex.offsets;
}

void miptex_t::stream_write(std::ostream &stream) const
{
    stream.write(reinterpret_cast<const char *>(data.data()), data.size());
}

// dmiptexlump_t

void dmiptexlump_t::stream_read(std::istream &stream, const lump_t &lump)
{
    int32_t nummiptex;
    stream >= nummiptex;

    // load in all of the offsets, we need them
    // to calculate individual data sizes
    std::vector<int32_t> offsets(nummiptex);

    for (size_t i = 0; i < nummiptex; i++) {
        stream >= offsets[i];
    }

    for (size_t i = 0; i < nummiptex; i++) {
        miptex_t &tex = textures.emplace_back();

        int32_t offset = offsets[i];

        // dummy texture?
        if (offset < 0) {
            tex.null_texture = true;
            continue;
        }

        // move to miptex position (technically required
        // because there might be dummy data between the offsets
        // and the mip textures themselves...)
        stream.seekg(lump.fileofs + offset);

        // calculate the length of the data used for the individual miptex.
        int32_t next_offset = -1;

        // scan forward (skipping -1's) to find the next valid offset
        for (int j = i + 1; j < nummiptex; ++j) {
            // valid?
            if (offsets[j] >= 0) {
                next_offset = offsets[j];
                break;
            }
        }
        if (next_offset == -1) {
            // the remainder of the texures are missing, so read to the end
            // of the overall lump
            next_offset = lump.filelen;
        }

        if (next_offset > offset) {
            tex.stream_read(stream, next_offset - offset);
        }
    }
}

void dmiptexlump_t::stream_write(std::ostream &stream) const
{
    auto p = (size_t)stream.tellp();

    stream <= static_cast<int32_t>(textures.size());

    const size_t header_size = sizeof(int32_t) + (sizeof(int32_t) * textures.size());

    size_t miptex_offset = 0;

    // write out the miptex offsets
    for (auto &texture : textures) {
        if (texture.null_texture) {
            // dummy texture
            stream <= static_cast<int32_t>(-1);
            continue;
        }

        stream <= static_cast<int32_t>(header_size + miptex_offset);

        miptex_offset += texture.stream_size();

        // Half Life requires the padding, but it's also a good idea
        // in general to keep them padded to 4s
        if ((p + miptex_offset) % 4) {
            miptex_offset += 4 - ((p + miptex_offset) % 4);
        }
    }

    for (auto &texture : textures) {
        if (!texture.null_texture) {
            // fix up the padding to match the above conditions
            if (stream.tellp() % 4) {
                constexpr const char pad[4]{};
                stream.write(pad, 4 - (stream.tellp() % 4));
            }
            texture.stream_write(stream);
        }
    }
}

size_t dmiptexlump_t::stream_size() const
{
    omemsizestream stream;
    stream_write(stream);
    return stream.tellp();
}

// dplane_t

void dplane_t::stream_write(std::ostream &s) const
{
    s <= std::tie(normal, dist, type);
}

void dplane_t::stream_read(std::istream &s)
{
    s >= std::tie(normal, dist, type);
}

// bsp2_dnode_t

void bsp2_dnode_t::stream_write(std::ostream &s) const
{
    s <= std::tie(planenum, children, mins, maxs, firstface, numfaces);
}
void bsp2_dnode_t::stream_read(std::istream &s)
{
    s >= std::tie(planenum, children, mins, maxs, firstface, numfaces);
}

// bsp2_dclipnode_t

void bsp2_dclipnode_t::stream_write(std::ostream &s) const
{
    s <= std::tie(planenum, children);
}

void bsp2_dclipnode_t::stream_read(std::istream &s)
{
    s >= std::tie(planenum, children);
}

// mleaf_t

static auto tuple(const mleaf_t &l)
{
    return std::tie(l.contents, l.visofs, l.mins, l.maxs, l.firstmarksurface, l.nummarksurfaces, l.ambient_level,
        l.cluster, l.area, l.firstleafbrush, l.numleafbrushes);
}

// darea_t

void darea_t::stream_write(std::ostream &s) const
{
    s <= std::tie(numareaportals, firstareaportal);
}

void darea_t::stream_read(std::istream &s)
{
    s >= std::tie(numareaportals, firstareaportal);
}

// dareaportal_t

void dareaportal_t::stream_write(std::ostream &s) const
{
    s <= std::tie(portalnum, otherarea);
}

void dareaportal_t::stream_read(std::istream &s)
{
    s >= std::tie(portalnum, otherarea);
}

// dbrush_t

void dbrush_t::stream_write(std::ostream &s) const
{
    s <= std::tie(firstside, numsides, contents);
}

void dbrush_t::stream_read(std::istream &s)
{
    s >= std::tie(firstside, numsides, contents);
}

// mbsp_t

int mbsp_t::lightsamples() const
{
    if (loadversion->game->has_rgb_lightmap) {
        return dlightdata.size() / 3;
    } else {
        return dlightdata.size();
    }
}
