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

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.
*/

#pragma once

#include "mathlib.hh"
#include "bspfile.hh"
#include "entdata.h"
#include "parser.hh"
#include <string>
#include <variant>
#include <optional>
#include <string_view>

// this file declares some names that clash with names elsewhere in the project and lead to ODR violations
// (e.g. texdef_valve_t). For now just wrap everything in a namespace to avoid issues.
namespace mapfile
{

// main brush style; technically these can be mixed
enum class texcoord_style_t
{
    quaked,
    etp,
    valve_220,
    brush_primitives
};

// raw texdef values; the unchanged
// values in the .map
struct texdef_bp_t
{
    qmat<double, 2, 3> axis;
};

struct texdef_quake_ed_t
{
    qvec2d shift;
    double rotate;
    qvec2d scale;

    auto operator<=>(const texdef_quake_ed_t &other) const = default;
    friend std::ostream &operator<<(std::ostream &os, const texdef_quake_ed_t &v);
};

struct texdef_valve_t
{
    qvec2d shift;
    double rotate;
    qvec2d scale;
    qmat<double, 2, 3> axis;

    auto operator<=>(const texdef_valve_t &other) const = default;
    friend std::ostream &operator<<(std::ostream &os, const texdef_valve_t &v);
};

struct texdef_etp_t : texdef_quake_ed_t
{
    bool tx2 = false;
};

// extra Q2 info
struct texinfo_quake2_t
{
    int contents;
    int flags;
    int value;
};

// convert a plane to a texture axis; used by quaked
struct texture_axis_t
{
    qvec3d xv;
    qvec3d yv;
    qvec3d snapped_normal;

    // use_new_axis = !qbsp_options.oldaxis.value()
    constexpr texture_axis_t(const qplane3d &plane, bool use_new_axis = false)
        : xv(), // gcc C++20 bug workaround
          yv(),
          snapped_normal()
    {
        constexpr qvec3d baseaxis[18] = {
            {0, 0, 1}, {1, 0, 0}, {0, -1, 0}, // floor
            {0, 0, -1}, {1, 0, 0}, {0, -1, 0}, // ceiling
            {1, 0, 0}, {0, 1, 0}, {0, 0, -1}, // west wall
            {-1, 0, 0}, {0, 1, 0}, {0, 0, -1}, // east wall
            {0, 1, 0}, {1, 0, 0}, {0, 0, -1}, // south wall
            {0, -1, 0}, {1, 0, 0}, {0, 0, -1} // north wall
        };

        double best = 0;
        size_t bestaxis = 0;

        for (size_t i = 0; i < 6; i++) {
            double dot = qv::dot(plane.normal, baseaxis[i * 3]);

            if (dot > best || (dot == best && use_new_axis)) {
                best = dot;
                bestaxis = i;
            }
        }

        xv = baseaxis[bestaxis * 3 + 1];
        yv = baseaxis[bestaxis * 3 + 2];
        snapped_normal = baseaxis[bestaxis * 3];
    }
};

// a single brush side from a .map
struct brush_side_t
{
    // source location
    parser_source_location location;

    // raw texture name
    std::string texture;
    // stores the original values that we loaded with, even if they were invalid.
    std::variant<texdef_quake_ed_t, texdef_valve_t, texdef_etp_t, texdef_bp_t> raw;
    // raw plane points
    std::array<qvec3d, 3> planepts;
    // Q2/Q3 data, if available
    std::optional<texinfo_quake2_t> extended_info = std::nullopt;

    // calculated texture vecs
    texvecf vecs;
    // calculated plane
    qplane3d plane;

    // TODO move to qv? keep local?
    static bool is_valid_texture_projection(const qvec3f &faceNormal, const texvecf &vecs);

    bool is_valid_texture_projection() const;
    void validate_texture_projection();

    // parsing
    // TODO: move to the individual texdefs?
    static texdef_bp_t parse_bp(parser_t &parser);
    static texdef_valve_t parse_valve_220(parser_t &parser);
    static texdef_quake_ed_t parse_quake_ed(parser_t &parser);

    bool parse_quark_comment(parser_t &parser);
    void parse_extended_texinfo(parser_t &parser);

    void set_texinfo(const texdef_quake_ed_t &texdef);
    void set_texinfo(const texdef_valve_t &texdef);
    void set_texinfo(const texdef_etp_t &texdef);
    void set_texinfo(const texdef_bp_t &texdef);

    void parse_texture_def(parser_t &parser, texcoord_style_t base_format);
    void parse_plane_def(parser_t &parser);

    void write_extended_info(std::ostream &stream);

    void write_texinfo(std::ostream &stream, const texdef_quake_ed_t &texdef);
    void write_texinfo(std::ostream &stream, const texdef_valve_t &texdef);
    void write_texinfo(std::ostream &stream, const texdef_etp_t &texdef);
    void write_texinfo(std::ostream &stream, const texdef_bp_t &texdef);

    void write(std::ostream &stream);

    void convert_to(texcoord_style_t style, const gamedef_t *game, const settings::common_settings &options);
};

struct brush_t
{
    parser_source_location location;
    texcoord_style_t base_format;
    std::vector<brush_side_t> faces;

    void parse_brush_face(parser_t &parser, texcoord_style_t base_format);

    void write(std::ostream &stream);

    void convert_to(texcoord_style_t style, const gamedef_t *game, const settings::common_settings &options);
};

struct map_entity_t
{
    parser_source_location location;
    entdict_t epairs;
    std::vector<brush_t> brushes;

    void parse_entity_dict(parser_t &parser);
    void parse_brush(parser_t &parser);
    bool parse(parser_t &parser);

    void write(std::ostream &stream);
};

struct map_file_t
{
    std::vector<map_entity_t> entities;

    void parse(parser_t &parser);

    void write(std::ostream &stream);

    void convert_to(texcoord_style_t style, const gamedef_t *game, const settings::common_settings &options);
};

map_file_t parse(std::string_view view, parser_source_location base_location);
map_file_t parse(const fs::data &data, parser_source_location base_location);

} // namespace mapfile
