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

#pragma once

#include <span>

#include <common/settings.hh>
#include <common/bsputils.hh> // for faceextents_t

#include <common/qvec.hh>

namespace img
{
struct texture;
}
struct mbsp_t;
struct mface_t;

constexpr float LIGHT_ON_EPSILON = 0.1f;
constexpr float LIGHT_ANGLE_EPSILON = 0.01f;
constexpr float LIGHT_EQUAL_EPSILON = 0.001f;

// FIXME: use maximum dimension of level
constexpr float MAX_SKY_DIST = 1000000;

struct lightsample_t
{
    qvec3f color;
    qvec3f direction;
};

// CHECK: isn't average a bad algorithm for color brightness?
template<typename T>
constexpr float LightSample_Brightness(const T &color)
{
    return ((color[0] + color[1] + color[2]) / 3.0);
}

/**
 * A directional light, emitted from "sky*" textured faces.
 */
class sun_t
{
public:
    qvec3f sunvec;
    float sunlight;
    qvec3f sunlight_color;
    bool dirt;
    float anglescale;
    int style;
    std::string suntexture;
    const img::texture *suntexture_value;
};

class modelinfo_t;
namespace settings
{
class worldspawn_keys;
};

class lightmap_t
{
public:
    int style;
    std::vector<lightsample_t> samples;
    qvec3f bounce_color;
};

using lightmapdict_t = std::vector<lightmap_t>;

struct surfacelight_t;
class raystream_occlusion_t;
class raystream_intersection_t;

struct lightsurf_t
{
    const settings::worldspawn_keys *cfg;
    const modelinfo_t *modelinfo;
    const mbsp_t *bsp;
    const mface_t *face;
    /* these take precedence the values in modelinfo */
    float minlight, maxlight, lightcolorscale = 1.0;
    float surflight_minlight_scale = 1.0;
    qvec3f minlight_color;
    bool nodirt, minlightMottle;
    bool curved; /*normals are interpolated for smooth lighting*/
    /* for lit water. receive light from either front or back. */
    bool twosided;
    int32_t object_channel_mask;

    qplane3f plane;
    qvec3f snormal;
    qvec3f tnormal;

    /* 16 in vanilla. engines will hate you if this is not power-of-two-and-at-least-one */
    float lightmapscale = 0.f;

    faceextents_t extents, vanilla_extents;

    // width * height sample points in world space
    struct sample_data_t
    {
        qvec3f point;
        qvec3f normal;
        bool occluded;
        int32_t realfacenum;
        /*
        raw ambient occlusion amount per sample point, 0-1, where 1 is
        fully occluded. dirtgain/dirtscale are not applied yet
        */
        float occlusion;
    };

    std::vector<sample_data_t> samples;

    /*
     pvs for the entire light surface. generated by ORing together
     the pvs at each of the sample points
     */
    std::vector<uint8_t> pvs;
    std::vector<const mleaf_t *> leaves;

    // output width * extra
    int width;
    // output height * extra
    int height;

    lightmapdict_t lightmapsByStyle;

    // surface light stuff
    std::unique_ptr<surfacelight_t> vpl;
};

/* debug */

enum class debugmodes
{
    none = 0,
    phong,
    phong_obj,
    dirt,
    bounce,
    bouncelights,
    debugoccluded,
    debugneighbours,
    phong_tangents,
    phong_bitangents,
    mottle
};

enum class lightfile_t
{
    // no auxiliary lighting formats
    none = 0,
    // write .lit (version 1) file with RGB lighting
    lit = 1,
    // write RGBLIGHTING BSPX lump
    bspx = 2,
    // write .lit (version 2) file
    lit2 = 4,
    // write .lit (version LIT_VERSION_E5BGR9) file with HDR lighting
    lithdr = 8,
    // write LIGHTING_E5BGR9 BSPX lump
    bspxhdr = 16,
    // bitmask for all HDR formats
    all_hdr_formats = (lithdr | bspxhdr)
};

enum class luxfile_t
{
    none = 0,
    // write .lux file
    lux = 1,
    // write LIGHTINGDIR BSPX lump
    bspx = 2
};

/* tracelist is a std::vector of pointers to modelinfo_t to use for LOS tests */
extern std::vector<const modelinfo_t *> tracelist;
extern std::vector<const modelinfo_t *> selfshadowlist;
extern std::vector<const modelinfo_t *> shadowworldonlylist;
extern std::vector<const modelinfo_t *> switchableshadowlist;

extern int numDirtVectors;

// other flags

extern bool dirt_in_use; // should any dirtmapping take place? set in SetupDirt

constexpr qvec3f vec3_white{255};

extern int dump_facenum;
extern int dump_vertnum;

constexpr int CHANNEL_MASK_DEFAULT = 1;

class modelinfo_t : public settings::setting_container
{
public:
    static constexpr float DEFAULT_PHONG_ANGLE = 89.0;

public:
    const mbsp_t *bsp;
    const dmodelh2_t *model;
    float lightmapscale;
    qvec3f offset;

    settings::setting_scalar minlight;
    // zero will apply no clamping; use lightignore instead to do that.
    // above zero, this controls the clamp value on the light, default 255
    settings::setting_scalar maxlight;
    settings::setting_bool minlightMottle;
    settings::setting_scalar shadow;
    settings::setting_scalar shadowself;
    settings::setting_scalar shadowworldonly;
    settings::setting_scalar switchableshadow;
    settings::setting_int32 switchshadstyle;
    settings::setting_scalar dirt;
    settings::setting_scalar phong;
    settings::setting_scalar phong_angle;
    settings::setting_scalar alpha;
    settings::setting_color minlight_color;
    settings::setting_bool lightignore;
    settings::setting_scalar lightcolorscale;
    settings::setting_int32 object_channel_mask;
    settings::setting_scalar surflight_minlight_scale;
    settings::setting_scalar surflight_atten;
    settings::setting_bool autominlight;
    settings::setting_string autominlight_target;

    float getResolvedPhongAngle() const;
    bool isWorld() const;

    modelinfo_t(const mbsp_t *b, const dmodelh2_t *m, float lmscale);
};

enum class visapprox_t
{
    NONE,
    AUTO,
    VIS,
    RAYS
};

enum class emissivequality_t
{
    LOW,
    MEDIUM,
    HIGH
};

enum class lightgrid_format_t
{
    OCTREE,
    LIGHTGRIDS,
};

//
// worldspawn keys / command-line settings
//

enum
{
    // Q1-style surface light copies
    SURFLIGHT_Q1 = 0,
    // Q2/Q3-style radiosity
    SURFLIGHT_RAD = 1
};

namespace settings
{
extern setting_group worldspawn_group;

class worldspawn_keys : public virtual setting_container
{
public:
    setting_scalar scaledist;
    setting_scalar rangescale;
    setting_scalar global_anglescale;
    setting_scalar lightmapgamma;
    setting_bool addminlight;
    setting_scalar minlight;
    setting_scalar minlightMottle;
    setting_scalar maxlight;
    setting_color minlight_color;
    setting_bool spotlightautofalloff;
    // start index for switchable light styles, default 32
    setting_int32 compilerstyle_start;
    // max index for switchable light styles, default 64
    setting_int32 compilerstyle_max;

    /* dirt */
    setting_bool dirt;
    setting_scalar dirtmode;
    setting_scalar dirtdepth;
    setting_scalar dirtscale;
    setting_scalar dirtgain;
    setting_scalar dirtangle;
    setting_bool minlight_dirt;

    /* phong */
    setting_bool phongallowed;
    setting_scalar phongangle;

    /* bounce */
    setting_int32 bounce;
    setting_bool bouncestyled;
    setting_scalar bouncescale;
    setting_scalar bouncecolorscale;
    setting_scalar bouncelightsubdivision;

    /* Q2 surface lights (mxd) */
    setting_scalar surflightscale;
    setting_scalar surflightskyscale;
    setting_scalar surflightskydist;
    // "choplight" - arghrad3 name
    setting_scalar surflightsubdivision;
    setting_scalar surflight_minlight_scale;
    setting_scalar surflight_atten;

    /* sunlight */
    /* sun_light, sun_color, sun_angle for http://www.bspquakeeditor.com/arghrad/ compatibility */
    setting_scalar sunlight;
    setting_color sunlight_color;
    setting_scalar sun2;
    setting_color sun2_color;
    setting_scalar sunlight2;
    setting_color sunlight2_color;
    setting_scalar sunlight3;
    setting_color sunlight3_color;
    setting_scalar sunlight_dirt;
    setting_scalar sunlight2_dirt;
    /* defaults to straight down */
    setting_mangle sunvec;
    /* defaults to straight down */
    setting_mangle sun2vec;
    setting_scalar sun_deviance;
    setting_color sky_surface;
    setting_int32 surflight_radiosity;

    worldspawn_keys();
};

extern setting_group output_group;
extern setting_group debug_group;
extern setting_group postprocessing_group;
extern setting_group experimental_group;

class light_settings : public common_settings, public worldspawn_keys
{
public:
    // slight modification to setting_numeric that supports
    // a default value if a non-number is supplied after parsing
    class setting_soft : public setting_int32
    {
    public:
        using setting_int32::setting_int32;

        bool parse(const std::string &setting_name, parser_base_t &parser, source source) override;
        std::string format() const override;
    };

    class setting_extra : public setting_value<int32_t>
    {
    public:
        using setting_value::setting_value;

        bool parse(const std::string &setting_name, parser_base_t &parser, source source) override;
        std::string string_value() const override;
        std::string format() const override;
    };

    void CheckNoDebugModeSet();

    setting_bool surflight_dump;
    setting_scalar surflight_subdivide;
    setting_bool onlyents;
    setting_bool write_normals;
    setting_bool novanilla;
    setting_scalar gate;
    setting_int32 sunsamples;
    setting_bool arghradcompat;
    setting_bool nolighting;
    setting_vec3 debugface;
    setting_vec3 debugvert;
    setting_bool highlightseams;
    setting_soft soft;
    setting_set radlights;
    setting_int32 lightmap_scale;
    setting_extra extra;
    setting_enum<emissivequality_t> emissivequality;
    setting_enum<visapprox_t> visapprox;
    setting_func lit;
    setting_func lit2;
    setting_func bspxlit;
    setting_func lux;
    setting_func bspxlux;
    setting_func bspxonly;
    setting_func bspx;
    setting_func hdr;
    setting_func bspxhdr;
    setting_scalar world_units_per_luxel;
    setting_bool litonly;
    setting_bool nolights;
    setting_int32 facestyles;
    setting_bool exportobj;
    setting_int32 lmshift;
    setting_bool lightgrid;
    setting_vec3 lightgrid_dist;
    setting_enum<lightgrid_format_t> lightgrid_format;

    setting_func dirtdebug;
    setting_func bouncedebug;
    setting_func bouncelightsdebug;
    setting_func phongdebug;
    setting_func phongdebug_obj;
    setting_func debugoccluded;
    setting_func debugneighbours;
    setting_func debugmottle;
    setting_bool debug_lightgrid_octree;

    light_settings();

    fs::path sourceMap;

    bitflags<lightfile_t> write_litfile = lightfile_t::none;
    bitflags<luxfile_t> write_luxfile = luxfile_t::none;
    debugmodes debugmode = debugmodes::none;

    void set_parameters(int argc, const char **argv) override;
    void initialize(int argc, const char **argv) override;
    void reset() override;

    // nb: split out because we want to print the summary later
    // since light loads stuff from worldspawn
    void light_postinitialize(int argc, const char **argv);
};
}; // namespace settings

extern settings::light_settings light_options;

const std::unordered_map<int, std::vector<uint8_t>> &UncompressedVis();

bool IsOutputtingSupplementaryData();

std::span<lightsurf_t> &LightSurfaces();
std::vector<lightsurf_t *> &EmissiveLightSurfaces();

extern std::vector<surfflags_t> extended_texinfo_flags;
extern std::vector<contentflags_t> extended_content_flags;

lightmap_t *Lightmap_ForStyle(lightmapdict_t *lightmaps, const int style, const lightsurf_t *lightsurf);

// public functions

void FixupGlobalSettings();
const modelinfo_t *ModelInfoForModel(const mbsp_t *bsp, int modelnum);
/**
 * returns nullptr for "skip" faces
 */
const modelinfo_t *ModelInfoForFace(const mbsp_t *bsp, int facenum);
const img::texture *Face_Texture(const mbsp_t *bsp, const mface_t *face);
const qvec3b &Face_LookupTextureColor(const mbsp_t *bsp, const mface_t *face);
const qvec3f &Face_LookupTextureBounceColor(const mbsp_t *bsp, const mface_t *face);
void SetLitNeeded(const bspdata_t &bspdata);
void light_reset();
int light_main(int argc, const char **argv);
int light_main(const std::vector<std::string> &args);
