//
//  Copyright (C) 1999 by
//  id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman
//  Copyright(C) 2015-2020 Fabian Greffrath
//
//  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.
//
// DESCRIPTION:
//      support extended nodes
//
//-----------------------------------------------------------------------------

#include <math.h>
#include <string.h>

#include "doomdata.h"
#include "doomstat.h"
#include "doomtype.h"
#include "i_printf.h"
#include "i_system.h"
#include "m_arena.h"
#include "m_argv.h"
#include "m_fixed.h"
#include "m_swap.h"
#include "p_bsp.h"
#include "p_setup.h"
#include "r_defs.h"
#include "r_main.h"
#include "r_state.h"
#include "w_wad.h"
#include "z_zone.h"

// [FG] support maps with NODES in compressed ZDBSP formats
#include "miniz.h"

#if defined(_MSC_VER)
  #pragma pack(push, 1)
#endif

// [FG] extended nodes
#define NF_SUBSECTOR_VANILLA 0x8000

// BSP node structure.
typedef struct PACKED_PREFIX
{
    short x; // Partition line from (x,y) to x+dx,y+dy)
    short y;
    short dx;
    short dy;
    // Bounding box for each child, clip against view frustum.
    short bbox[2][4];
    // If NF_SUBSECTOR its a subsector, else it's a node of another subtree.
    unsigned short children[2];
} PACKED_SUFFIX mapnode_t;

// SubSector, as generated by BSP.
typedef struct PACKED_PREFIX
{
    unsigned short numsegs;
    // Index of first one; segs are stored sequentially.
    unsigned short firstseg;
} PACKED_SUFFIX mapsubsector_t;

// LineSeg, generated by splitting LineDefs
// using partition lines selected by BSP builder.
typedef struct PACKED_PREFIX
{
    unsigned short v1;
    unsigned short v2;
    short angle;
    unsigned short linedef;
    short side;
    short offset;
} PACKED_SUFFIX mapseg_t;

// [FG] support maps with DeePBSPV4 nodes
typedef PACKED_PREFIX struct
{
    int v1;
    int v2;
    unsigned short angle;
    unsigned short linedef;
    short side;
    unsigned short offset;
} PACKED_SUFFIX mapseg_deepbspv4_t;

typedef PACKED_PREFIX struct
{
    short x;
    short y;
    short dx;
    short dy;
    short bbox[2][4];
    int children[2];
} PACKED_SUFFIX mapnode_deepbspv4_t;

typedef PACKED_PREFIX struct
{
    unsigned short numsegs;
    int firstseg;
} PACKED_SUFFIX mapsubsector_deepbspv4_t;

// [FG] support maps with ZDBSP nodes
typedef PACKED_PREFIX struct
{
    unsigned int v1, v2;
    unsigned short linedef;
    unsigned char side;
} PACKED_SUFFIX mapseg_xnod_t;

typedef PACKED_PREFIX struct
{
    short x;
    short y;
    short dx;
    short dy;
    short bbox[2][4];
    int children[2];
} PACKED_SUFFIX mapnode_xnod_t;

typedef PACKED_PREFIX struct
{
    unsigned int numsegs;
} PACKED_SUFFIX mapsubsector_xnod_t;

typedef PACKED_PREFIX struct
{
    unsigned int vertex, partner;
    unsigned short linedef;
    unsigned char side;
} PACKED_SUFFIX mapseg_xgln_t;

typedef PACKED_PREFIX struct
{
    unsigned int vertex, partner;
    unsigned int linedef;
    unsigned char side;
} PACKED_SUFFIX mapseg_xgl2_t;

typedef PACKED_PREFIX struct
{
    int x;
    int y;
    int dx;
    int dy;
    short bbox[2][4];
    int children[2];
} PACKED_SUFFIX mapnode_xgl3_t;

#if defined(_MSC_VER)
  #pragma pack(pop)
#endif

void P_InitSubsectorLines(void)
{
    int count;

    if (sslines_indexes)
    {
        free(sslines_indexes);
        sslines_indexes = NULL;
    }

    if (sslines)
    {
        free(sslines);
        sslines = NULL;
    }

    count = 0;
    sslines_indexes = malloc((numsubsectors + 1) * sizeof(sslines_indexes[0]));

    for (int num = 0; num < numsubsectors; num++)
    {
        seg_t *seg;
        const seg_t *seg_last =
            segs + subsectors[num].firstline + subsectors[num].numlines;

        sslines_indexes[num] = count;

        for (seg = segs + subsectors[num].firstline; seg < seg_last; seg++)
        {
            if (!seg->linedef)
            {
                continue;
            }
            seg->linedef->validcount = 0;
        }

        for (seg = segs + subsectors[num].firstline; seg < seg_last; seg++)
        {
            if (!seg->linedef)
            {
                continue;
            }

            if (seg->linedef->validcount == 1)
            {
                continue;
            }

            seg->linedef->validcount = 1;
            count++;
        }
    }

    sslines_indexes[numsubsectors] = count;

    sslines = malloc(count * sizeof(sslines[0]));
    count = 0;

    for (int num = 0; num < numsubsectors; num++)
    {
        seg_t *seg;
        const seg_t *seg_last =
            segs + subsectors[num].firstline + subsectors[num].numlines;

        for (seg = segs + subsectors[num].firstline; seg < seg_last; seg++)
        {
            if (!seg->linedef)
            {
                continue;
            }
            seg->linedef->validcount = 0;
        }

        for (seg = segs + subsectors[num].firstline; seg < seg_last; seg++)
        {
            ssline_t *ssline = &sslines[count];
            if (!seg->linedef)
            {
                continue;
            }

            if (seg->linedef->validcount == 1)
            {
                continue;
            }

            seg->linedef->validcount = 1;

            ssline->seg = seg;
            ssline->linedef = seg->linedef;

            ssline->x1 = seg->linedef->v1->x;
            ssline->y1 = seg->linedef->v1->y;
            ssline->x2 = seg->linedef->v2->x;
            ssline->y2 = seg->linedef->v2->y;
            ssline->bbox[0] = seg->linedef->bbox[0];
            ssline->bbox[1] = seg->linedef->bbox[1];
            ssline->bbox[2] = seg->linedef->bbox[2];
            ssline->bbox[3] = seg->linedef->bbox[3];

            count++;
        }
    }

    for (int num = 0; num < numlines; num++)
    {
        lines[num].validcount = 0;
    }
}

// [FG] support extended nodes

void P_CheckBSPFormat_Binary(map_t *map)
{
    map->bsp_format = BSP_DOOMBSP;
    int size_subs = 0, size_nodes = 0;

    //!
    // @category mod
    //
    // Forces rebuilding of nodes.
    //

    if (M_CheckParm("-bsp"))
    {
        map->bsp_format = BSP_NANO;
        return;
    }

    //!
    // @category mod
    //
    // Forces extended (non-GL) ZDoom nodes.
    //

    if (!M_CheckParm("-force_old_zdoom_nodes"))
    {
        size_subs = W_LumpLengthWithName(map->ssectors, "SSECTORS");

        if (size_subs >= sizeof(mapsubsector_t))
        {
            byte *lump_data = W_CacheLumpNum(map->ssectors, PU_STATIC);

            if (!memcmp(lump_data, "XGLN", 4))
            {
                map->bsp_format = BSP_XGLN;
                map->znodes = map->ssectors;
            }
            else if (!memcmp(lump_data, "ZGLN", 4))
            {
                map->bsp_format = BSP_ZGLN;
                map->znodes = map->ssectors;
            }
            else if (!memcmp(lump_data, "XGL2", 4))
            {
                map->bsp_format = BSP_XGL2;
                map->znodes = map->ssectors;
            }
            else if (!memcmp(lump_data, "ZGL2", 4))
            {
                map->bsp_format = BSP_ZGL2;
                map->znodes = map->ssectors;
            }
            else if (!memcmp(lump_data, "XGL3", 4))
            {
                map->bsp_format = BSP_XGL3;
                map->znodes = map->ssectors;
            }
            else if (!memcmp(lump_data, "ZGL3", 4))
            {
                map->bsp_format = BSP_ZGL3;
                map->znodes = map->ssectors;
            }

            Z_Free(lump_data);
        }
        else
        {
            map->bsp_format = BSP_NANO;
        }
    }

    if (map->bsp_format == BSP_DOOMBSP || map->bsp_format == BSP_NANO)
    {
        size_nodes = W_LumpLengthWithName(map->nodes, "NODES");

        if (size_nodes >= sizeof(mapnode_t))
        {
            byte *lump_data = W_CacheLumpNum(map->nodes, PU_STATIC);

            if (!memcmp(lump_data, "xNd4\0\0\0\0", 8))
            {
                map->bsp_format = BSP_DEEPBSPV4;
            }
            else if (!memcmp(lump_data, "XNOD", 4))
            {
                map->bsp_format = BSP_XNOD;
                map->znodes = map->nodes;
            }
            else if (!memcmp(lump_data, "ZNOD", 4))
            {
                map->bsp_format = BSP_ZNOD;
                map->znodes = map->nodes;
            }

            Z_Free(lump_data);
        }
        else
        {
            map->bsp_format = BSP_NANO;
        }
    }

    // [FG] no nodes for exactly one subsector
    if (size_subs == sizeof(mapsubsector_t) && size_nodes == 0)
    {
        map->bsp_format = BSP_DOOMBSP;
    }
}

void P_CheckBSPFormat_UDMF(map_t *map)
{
    byte *lump_data = W_CacheLumpNum(map->znodes, PU_STATIC);
    if (!memcmp(lump_data, "XNOD", 4))
    {
        map->bsp_format = BSP_XNOD;
    }
    else if (!memcmp(lump_data, "ZNOD", 4))
    {
        map->bsp_format = BSP_ZNOD;
    }
    else if (!memcmp(lump_data, "XGLN", 4))
    {
        map->bsp_format = BSP_XGLN;
    }
    else if (!memcmp(lump_data, "ZGLN", 4))
    {
        map->bsp_format = BSP_ZGLN;
    }
    else if (!memcmp(lump_data, "XGL2", 4))
    {
        map->bsp_format = BSP_XGL2;
    }
    else if (!memcmp(lump_data, "ZGL2", 4))
    {
        map->bsp_format = BSP_ZGL2;
    }
    else if (!memcmp(lump_data, "XGL3", 4))
    {
        map->bsp_format = BSP_XGL3;
    }
    else if (!memcmp(lump_data, "ZGL3", 4))
    {
        map->bsp_format = BSP_ZGL3;
    }
    Z_Free(lump_data);
}

// [FG] recalculate seg offsets

int P_GetOffset(vertex_t *v1, vertex_t *v2)
{
    float a, b;
    int r;

    a = (float)(v1->x - v2->x) / (float)FRACUNIT;
    b = (float)(v1->y - v2->y) / (float)FRACUNIT;
    r = (int)(sqrt(a * a + b * b) * (float)FRACUNIT);

    return r;
}

// [FG] extended nodes

//
// P_LoadNodes
//
// killough 5/3/98: reformatted, cleaned up

void P_LoadNodes(int lump)
{
    byte *data;

    numnodes = W_LumpLength(lump) / sizeof(mapnode_t);
    nodes = Z_Malloc(numnodes * sizeof(node_t), PU_LEVEL, 0);
    data = W_CacheLumpNum(lump, PU_STATIC);

    for (int i = 0; i < numnodes; i++)
    {
        node_t *no = nodes + i;
        mapnode_t *mn = (mapnode_t *)data + i;

        no->x = IntToFixed(SHORT(mn->x));
        no->y = IntToFixed(SHORT(mn->y));
        no->dx = IntToFixed(SHORT(mn->dx));
        no->dy = IntToFixed(SHORT(mn->dy));

        for (int j = 0; j < 2; j++)
        {
            no->children[j] = (unsigned short)SHORT(mn->children[j]);

            if (no->children[j] == NO_INDEX_SHORT)
            {
                no->children[j] = NO_INDEX;
            }
            else if (no->children[j] & NF_SUBSECTOR_VANILLA)
            {
                no->children[j] &= ~NF_SUBSECTOR_VANILLA;

                if (no->children[j] >= numsubsectors)
                {
                    no->children[j] = 0;
                }

                no->children[j] |= NF_SUBSECTOR;
            }

            for (int k = 0; k < 4; k++)
            {
                no->bbox[j][k] = IntToFixed(SHORT(mn->bbox[j][k]));
            }
        }
    }

    Z_Free(data);
}

//
// P_LoadSubsectors
//
// killough 5/3/98: reformatted, cleaned up

void P_LoadSubsectors(int lump)
{
    byte *data;

    numsubsectors = W_LumpLength(lump) / sizeof(mapsubsector_t);
    subsectors = arena_alloc_num(world_arena, subsector_t, numsubsectors);
    data = W_CacheLumpNum(lump, PU_STATIC);

    memset(subsectors, 0, numsubsectors * sizeof(subsector_t));

    for (int i = 0; i < numsubsectors; i++)
    {
        // [FG] extended nodes
        subsectors[i].numlines =
            (unsigned short)SHORT(((mapsubsector_t *)data)[i].numsegs);
        subsectors[i].firstline =
            (unsigned short)SHORT(((mapsubsector_t *)data)[i].firstseg);
    }

    Z_Free(data);
}

//
// P_LoadSegs
//
// killough 5/3/98: reformatted, cleaned up

void P_LoadSegs(int lump)
{
    byte *data;

    numsegs = W_LumpLength(lump) / sizeof(mapseg_t);
    segs = arena_alloc_num(world_arena, seg_t, numsegs);
    data = W_CacheLumpNum(lump, PU_STATIC);

    for (int i = 0; i < numsegs; i++)
    {
        seg_t *li = segs + i;
        mapseg_t *ml = (mapseg_t *)data + i;
        unsigned int v1, v2;
        int side, linedef;
        line_t *ldef;

        // [FG] extended nodes
        v1 = (unsigned short)SHORT(ml->v1);
        v2 = (unsigned short)SHORT(ml->v2);

        li->angle = IntToFixed(SHORT(ml->angle));
        linedef = (unsigned short)SHORT(ml->linedef); // [FG] extended nodes

        // Andrey Budko: check for wrong indexes
        if ((unsigned)linedef >= (unsigned)numlines)
        {
            I_Error("seg %d references a non-existent linedef %d", i,
                    (unsigned)linedef);
        }

        ldef = &lines[linedef];
        li->linedef = ldef;
        side = SHORT(ml->side);

        // Andrey Budko: check for wrong indexes
        if ((unsigned)ldef->sidenum[side] >= (unsigned)numsides)
        {
            I_Error(
                "linedef %d for seg %d references a non-existent sidedef %d",
                linedef, i, (unsigned)ldef->sidenum[side]);
        }

        li->sidedef = &sides[ldef->sidenum[side]];

        /* cph 2006/09/30 - our frontsector can be the second side of the
         * linedef, so must check for NO_INDEX in case we are incorrectly
         * referencing the back of a 1S line */
        if (ldef->sidenum[side] != NO_INDEX)
        {
            li->frontsector = sides[ldef->sidenum[side]].sector;
        }
        else
        {
            li->frontsector = 0;
            I_Printf(VB_DEBUG, "%s: front of seg %i has no sidedef", __func__,
                     i);
        }

        if (ldef->flags & ML_TWOSIDED)
        {
            int sidenum = ldef->sidenum[side ^ 1];

            if (sidenum == NO_INDEX)
            {
                // this is wrong
                li->backsector = GetSectorAtNullAddress();
            }
            else
            {
                li->backsector = sides[sidenum].sector;
            }
        }
        else
        {
            li->backsector = 0;
        }

        // Andrey Budko
        // check and fix wrong references to non-existent vertexes
        // see e1m9 @ NIVELES.WAD
        // http://www.doomworld.com/idgames/index.php?id=12647
        if (v1 >= numvertexes || v2 >= numvertexes)
        {
            if (v1 >= numvertexes)
            {
                I_Printf(VB_WARNING,
                         "%s: compatibility loss - seg %d "
                         "references a non-existent vertex %d.",
                         __func__, i, v1);
            }

            if (v2 >= numvertexes)
            {
                I_Printf(VB_WARNING,
                         "%s: compatibility loss - seg %d "
                         "references a non-existent vertex %d.",
                         __func__, i, v2);
            }

            if (demorecording)
            {
                I_Error("Demo recording on levels with invalid "
                        "nodes is not allowed.");
            }

            if (li->sidedef == &sides[li->linedef->sidenum[0]])
            {
                li->v1 = lines[ml->linedef].v1;
                li->v2 = lines[ml->linedef].v2;
            }
            else
            {
                li->v1 = lines[ml->linedef].v2;
                li->v2 = lines[ml->linedef].v1;
            }
        }
        else
        {
            li->v1 = &vertexes[v1];
            li->v2 = &vertexes[v2];
        }

        // [FG] recalculate
        li->offset = P_GetOffset(li->v1, (ml->side ? ldef->v2 : ldef->v1));
    }

    Z_Free(data);
}

// [FG] support maps with DeePBSP nodes

void P_LoadSegs_DeePBSPV4(int lump)
{
    byte *data;

    numsegs = W_LumpLength(lump) / sizeof(mapseg_deepbspv4_t);
    segs = arena_alloc_num(world_arena, seg_t, numsegs);
    data = W_CacheLumpNum(lump, PU_STATIC);

    for (int i = 0; i < numsegs; i++)
    {
        seg_t *li = segs + i;
        mapseg_deepbspv4_t *ml = (mapseg_deepbspv4_t *)data + i;
        int side, linedef;
        line_t *ldef;
        int v1, v2;

        // [MB] 2020-04-22: Fix endianess for DeePBSPV4 nodes
        v1 = LONG(ml->v1);
        v2 = LONG(ml->v2);

        li->angle = IntToFixed(SHORT(ml->angle));

        linedef = (unsigned short)SHORT(ml->linedef); // [FG] extended nodes

        // Andrey Budko: check for wrong indexes
        if ((unsigned)linedef >= (unsigned)numlines)
        {
            I_Error("seg %d references a non-existent linedef %d", i,
                    (unsigned)linedef);
        }

        ldef = &lines[linedef];
        li->linedef = ldef;

        side = SHORT(ml->side);
        // Andrey Budko: fix wrong side index
        if (side != 0 && side != 1)
        {
            side = 1;
        }

        li->sidedef = &sides[ldef->sidenum[side]];

        /* cph 2006/09/30 - our frontsector can be the second side of the
         * linedef, so must check for NO_INDEX in case we are incorrectly
         * referencing the back of a 1S line */
        if (ldef->sidenum[side] != NO_INDEX)
        {
            li->frontsector = sides[ldef->sidenum[side]].sector;
        }
        else
        {
            li->frontsector = 0;
            I_Printf(VB_DEBUG, "%s: front of seg %i has no sidedef", __func__,
                     i);
        }

        if (ldef->flags & ML_TWOSIDED && ldef->sidenum[side ^ 1] != NO_INDEX)
        {
            li->backsector = sides[ldef->sidenum[side ^ 1]].sector;
        }
        else
        {
            li->backsector = 0;
        }

        // Andrey Budko
        // check and fix wrong references to non-existent vertexes
        // see e1m9 @ NIVELES.WAD
        // http://www.doomworld.com/idgames/index.php?id=12647
        if (v1 >= numvertexes || v2 >= numvertexes)
        {
            if (v1 >= numvertexes)
            {
                I_Printf(VB_WARNING,
                         "%s: compatibility loss - seg %d "
                         "references a non-existent vertex %d.",
                         __func__, i, v1);
            }

            if (v2 >= numvertexes)
            {
                I_Printf(VB_WARNING,
                         "%s: compatibility loss - seg %d "
                         "references a non-existent vertex %d.",
                         __func__, i, v2);
            }

            if (demorecording)
            {
                I_Error("Demo recording on levels with invalid "
                        "nodes is not allowed.");
            }

            if (li->sidedef == &sides[li->linedef->sidenum[0]])
            {
                li->v1 = lines[ml->linedef].v1;
                li->v2 = lines[ml->linedef].v2;
            }
            else
            {
                li->v1 = lines[ml->linedef].v2;
                li->v2 = lines[ml->linedef].v1;
            }
        }
        else
        {
            li->v1 = &vertexes[v1];
            li->v2 = &vertexes[v2];
        }

        // [FG] recalculate
        li->offset = P_GetOffset(li->v1, (ml->side ? ldef->v2 : ldef->v1));
    }

    Z_Free(data);
}

void P_LoadSubsectors_DeePBSPV4(int lump)
{
    mapsubsector_deepbspv4_t *data;
    int i;

    numsubsectors = W_LumpLength(lump) / sizeof(mapsubsector_deepbspv4_t);
    subsectors = arena_alloc_num(world_arena, subsector_t, numsubsectors);
    data = (mapsubsector_deepbspv4_t *)W_CacheLumpNum(lump, PU_STATIC);

    memset(subsectors, 0, numsubsectors * sizeof(subsector_t));

    for (i = 0; i < numsubsectors; i++)
    {
        // [MB] 2020-04-22: Fix endianess for DeePBSPV4 nodes
        subsectors[i].numlines = (unsigned short)SHORT(data[i].numsegs);
        subsectors[i].firstline = LONG(data[i].firstseg);
    }

    Z_Free(data);
}

void P_LoadNodes_DeePBSPV4(int lump)
{
    byte *data;
    int i;

    numnodes = (W_LumpLength(lump) - 8) / sizeof(mapnode_deepbspv4_t);
    nodes = Z_Malloc(numnodes * sizeof(node_t), PU_LEVEL, 0);
    data = W_CacheLumpNum(lump, PU_STATIC);

    // [FG] skip header
    data += 8;

    for (i = 0; i < numnodes; i++)
    {
        node_t *no = nodes + i;
        mapnode_deepbspv4_t *mn = (mapnode_deepbspv4_t *)data + i;
        int j;

        no->x = SHORT(mn->x) << FRACBITS;
        no->y = SHORT(mn->y) << FRACBITS;
        no->dx = SHORT(mn->dx) << FRACBITS;
        no->dy = SHORT(mn->dy) << FRACBITS;

        for (j = 0; j < 2; j++)
        {
            int k;

            // [MB] 2020-04-22: Fix endianess for DeePBSPV4 nodes
            no->children[j] = LONG(mn->children[j]);

            for (k = 0; k < 4; k++)
            {
                no->bbox[j][k] = SHORT(mn->bbox[j][k]) << FRACBITS;
            }
        }
    }

    W_CacheLumpNum(lump, PU_CACHE);
}

// [FG] support maps with ZDBSP nodes

static void P_LoadSegs_XNOD(byte *data)
{
    int i;

    for (i = 0; i < numsegs; i++)
    {
        line_t *ldef;
        unsigned int linedef;
        unsigned char side;
        seg_t *li = segs + i;
        mapseg_xnod_t *ml = (mapseg_xnod_t *)data + i;
        unsigned int v1, v2;

        v1 = LONG(ml->v1);
        v2 = LONG(ml->v2);
        li->v1 = &vertexes[v1];
        li->v2 = &vertexes[v2];

        linedef = (unsigned short)SHORT(ml->linedef);
        // Andrey Budko: check for wrong indexes
        if ((unsigned)linedef >= (unsigned)numlines)
        {
            I_Error("seg %d references a non-existent linedef %d", i,
                    (unsigned)linedef);
        }

        ldef = &lines[linedef];
        li->linedef = ldef;
        side = ml->side;

        // Andrey Budko: fix wrong side index
        if (side != 0 && side != 1)
        {
            side = 1;
        }

        // Andrey Budko: check for wrong indexes
        if ((unsigned)ldef->sidenum[side] >= (unsigned)numsides)
        {
            I_Error(
                "linedef %d for seg %d references a non-existent sidedef %d",
                linedef, i, (unsigned)ldef->sidenum[side]);
        }

        li->sidedef = &sides[ldef->sidenum[side]];

        /* cph 2006/09/30 - our frontsector can be the second side of the
         * linedef, so must check for NO_INDEX in case we are incorrectly
         * referencing the back of a 1S line */
        if (ldef->sidenum[side] != NO_INDEX)
        {
            li->frontsector = sides[ldef->sidenum[side]].sector;
        }
        else
        {
            li->frontsector = 0;
            I_Printf(VB_DEBUG, "%s: front of seg %i has no sidedef\n", __func__,
                     i);
        }

        // seg angle and offset are not included
        li->angle = R_PointToAngle2(li->v1->x, li->v1->y, li->v2->x, li->v2->y);
        li->offset = P_GetOffset(li->v1, (ml->side ? ldef->v2 : ldef->v1));

        if (ldef->flags & ML_TWOSIDED && ldef->sidenum[side ^ 1] != NO_INDEX)
        {
            li->backsector = sides[ldef->sidenum[side ^ 1]].sector;
        }
        else
        {
            li->backsector = 0;
        }
    }
}

static void P_LoadSegs_XGL(byte *data, bsp_format_t format)
{
    int i, j;
    const mapseg_xgln_t *mln = (const mapseg_xgln_t *)data;
    const mapseg_xgl2_t *ml2 = (const mapseg_xgl2_t *)data;

    for (i = 0; i < numsubsectors; ++i)
    {
        for (j = 0; j < subsectors[i].numlines; ++j)
        {
            unsigned int v1;
            // unsigned int partner;
            unsigned int line;
            unsigned char side;
            seg_t *seg;

            if (format == BSP_XGLN || format == BSP_ZGLN)
            {
                v1 = LONG(mln->vertex);
                // partner = LONG(mln->partner);
                line = (unsigned short)SHORT(mln->linedef);
                side = mln->side;
                FIX_NO_INDEX(line);
                mln++;
            }
            else
            {
                v1 = LONG(ml2->vertex);
                // partner = LONG(ml2->partner);
                line = (unsigned int)LONG(ml2->linedef);
                side = ml2->side;
                ml2++;
            }

            seg = &segs[subsectors[i].firstline + j];

            seg->v1 = &vertexes[v1];
            if (j == 0)
            {
                seg[subsectors[i].numlines - 1].v2 = seg->v1;
            }
            else
            {
                seg[-1].v2 = seg->v1;
            }

            if (line != NO_INDEX)
            {
                line_t *ldef;

                if ((unsigned int)line >= (unsigned int)numlines)
                {
                    I_Error("seg %d, %d references a non-existent linedef %d",
                            i, j, (unsigned int)line);
                }

                ldef = &lines[line];
                seg->linedef = ldef;

                if (side != 0 && side != 1)
                {
                    I_Error("seg %d, %d references a non-existent side %d", i,
                            j, (unsigned int)side);
                }

                if ((unsigned)ldef->sidenum[side] >= (unsigned)numsides)
                {
                    I_Error("linedef %d for seg %d, %d "
                            "references a non-existent sidedef %d",
                            line, i, j, (unsigned)ldef->sidenum[side]);
                }

                seg->sidedef = &sides[ldef->sidenum[side]];

                /* cph 2006/09/30 - our frontsector can be the second side of
                 * the linedef, so must check for NO_INDEX in case we are
                 * incorrectly referencing the back of a 1S line */
                if (ldef->sidenum[side] != NO_INDEX)
                {
                    seg->frontsector = sides[ldef->sidenum[side]].sector;
                }
                else
                {
                    seg->frontsector = 0;
                    I_Printf(VB_WARNING,
                             "%s: front of seg %d, %d has no sidedef", __func__,
                             i, j);
                }

                if (ldef->flags & ML_TWOSIDED
                    && (ldef->sidenum[side ^ 1] != NO_INDEX))
                {
                    seg->backsector = sides[ldef->sidenum[side ^ 1]].sector;
                }
                else
                {
                    seg->backsector = 0;
                }

                seg->offset =
                    P_GetOffset(seg->v1, (side ? ldef->v2 : ldef->v1));
            }
            else
            {
                seg->angle = 0;
                seg->offset = 0;
                seg->linedef = NULL;
                seg->sidedef = NULL;
                seg->frontsector = segs[subsectors[i].firstline].frontsector;
                seg->backsector = seg->frontsector;
            }
        }

        // Need all vertices to be defined before setting angles
        for (j = 0; j < subsectors[i].numlines; ++j)
        {
            seg_t *seg = &segs[subsectors[i].firstline + j];

            if (seg->linedef)
            {
                seg->angle = R_PointToAngle2(seg->v1->x, seg->v1->y, seg->v2->x,
                                             seg->v2->y);
            }
        }
    }
}

void P_LoadBSPTree_ZDBSP(int lump, bsp_format_t format)
{
    byte *data;
    unsigned int i;
    byte *output = NULL;

    unsigned int orgVerts, newVerts;
    unsigned int numSubs, currSeg;
    unsigned int numSegs;
    unsigned int numNodes;
    vertex_t *newvertarray = NULL;

    data = W_CacheLumpNum(lump, PU_LEVEL);

    // 0. Uncompress nodes lump (or simply skip header)
    boolean compressed = format == BSP_ZNOD || format == BSP_ZGLN
                         || format == BSP_ZGL2 || format == BSP_ZGL3;

    if (compressed)
    {
        const int len = W_LumpLength(lump);
        int outlen, err;
        z_stream *zstream;

        // first estimate for compression rate:
        // output buffer size == 2.5 * input size
        outlen = 2.5 * len;
        output = Z_Malloc(outlen, PU_STATIC, 0);

        // initialize stream state for decompression
        zstream = Z_Malloc(sizeof(*zstream), PU_STATIC, 0);
        memset(zstream, 0, sizeof(*zstream));
        zstream->next_in = data + 4;
        zstream->avail_in = len - 4;
        zstream->next_out = output;
        zstream->avail_out = outlen;

        if (inflateInit(zstream) != Z_OK)
        {
            I_Error("Error during ZNOD nodes decompression initialization!");
        }

        // resize if output buffer runs full
        while ((err = inflate(zstream, Z_SYNC_FLUSH)) == Z_OK)
        {
            int outlen_old = outlen;
            outlen = 2 * outlen_old;
            output = Z_Realloc(output, outlen, PU_STATIC, 0);
            zstream->next_out = output + outlen_old;
            zstream->avail_out = outlen - outlen_old;
        }

        if (err != Z_STREAM_END)
        {
            I_Error("Error during ZNOD nodes decompression!");
        }

        I_Printf(VB_DEBUG,
                 "P_LoadBSPTree_ZDBSP: ZNOD nodes compression ratio %.3f",
                 (float)zstream->total_out / zstream->total_in);

        data = output;

        if (inflateEnd(zstream) != Z_OK)
        {
            I_Error("Error during ZNOD nodes decompression shut-down!");
        }

        // release the original data lump
        W_CacheLumpNum(lump, PU_CACHE);
        Z_Free(zstream);
    }
    else
    {
        // skip header
        data += 4;
    }

    // 1. Load new vertices added during node building

    orgVerts = LONG(*((unsigned int *)data));
    data += sizeof(orgVerts);

    newVerts = LONG(*((unsigned int *)data));
    data += sizeof(newVerts);

    if (orgVerts + newVerts == (unsigned int)numvertexes)
    {
        newvertarray = vertexes;
    }
    else
    {
        newvertarray =
            arena_alloc_num(world_arena, vertex_t, orgVerts + newVerts);
        memcpy(newvertarray, vertexes, orgVerts * sizeof(vertex_t));
        memset(newvertarray + orgVerts, 0, newVerts * sizeof(vertex_t));
    }

    for (i = 0; i < newVerts; i++)
    {
        newvertarray[i + orgVerts].r_x = newvertarray[i + orgVerts].x =
            LONG(*((unsigned int *)data));
        data += sizeof(newvertarray[0].x);

        newvertarray[i + orgVerts].r_y = newvertarray[i + orgVerts].y =
            LONG(*((unsigned int *)data));
        data += sizeof(newvertarray[0].y);
    }

    if (vertexes != newvertarray)
    {
        for (i = 0; i < (unsigned int)numlines; i++)
        {
            lines[i].v1 = lines[i].v1 - vertexes + newvertarray;
            lines[i].v2 = lines[i].v2 - vertexes + newvertarray;
        }

        vertexes = newvertarray;
        numvertexes = orgVerts + newVerts;
    }

    // 2. Load subsectors

    numSubs = LONG(*((unsigned int *)data));
    data += sizeof(numSubs);

    if (numSubs < 1)
    {
        I_Error("No subsectors in map!");
    }

    numsubsectors = numSubs;
    subsectors = arena_alloc_num(world_arena, subsector_t, numsubsectors);

    for (i = currSeg = 0; i < numsubsectors; i++)
    {
        mapsubsector_xnod_t *mseg = (mapsubsector_xnod_t *)data + i;

        subsectors[i].firstline = currSeg;
        subsectors[i].numlines = LONG(mseg->numsegs);
        currSeg += LONG(mseg->numsegs);
    }

    data += numsubsectors * sizeof(mapsubsector_xnod_t);

    // 3. Load segs

    numSegs = LONG(*((unsigned int *)data));
    data += sizeof(numSegs);

    // The number of stored segs should match the number of segs used by
    // subsectors
    if (numSegs != currSeg)
    {
        I_Error("Incorrect number of segs in XNOD nodes!");
    }

    numsegs = numSegs;
    segs = arena_alloc_num(world_arena, seg_t, numsegs);

    if (format == BSP_XNOD || format == BSP_ZNOD)
    {
        P_LoadSegs_XNOD(data);
        data += numsegs * sizeof(mapseg_xnod_t);
    }
    else if (format == BSP_XGLN || format == BSP_ZGLN)
    {
        P_LoadSegs_XGL(data, format);
        data += numsegs * sizeof(mapseg_xgln_t);
    }
    else if (format == BSP_XGL2 || format == BSP_ZGL2 || format == BSP_XGL3
             || format == BSP_ZGL3)
    {
        P_LoadSegs_XGL(data, format);
        data += numsegs * sizeof(mapseg_xgl2_t);
    }

    // 4. Load nodes

    numNodes = LONG(*((unsigned int *)data));
    data += sizeof(numNodes);

    numnodes = numNodes;
    nodes = Z_Malloc(numnodes * sizeof(node_t), PU_LEVEL, 0);

    for (i = 0; i < numnodes; i++)
    {
        int j, k;
        node_t *no = nodes + i;

        if (format == BSP_XGL3 || format == BSP_ZGL3)
        {
            const mapnode_xgl3_t *mn3 = (const mapnode_xgl3_t *)data + i;
            no->x = LONG(mn3->x);
            no->y = LONG(mn3->y);
            no->dx = LONG(mn3->dx);
            no->dy = LONG(mn3->dy);
            for (j = 0; j < 2; j++)
            {
                no->children[j] = LONG(mn3->children[j]);
                for (k = 0; k < 4; k++)
                {
                    no->bbox[j][k] = SHORT(mn3->bbox[j][k]) << FRACBITS;
                }
            }
        }
        else
        {
            const mapnode_xnod_t *mn = (const mapnode_xnod_t *)data + i;
            no->x = SHORT(mn->x) << FRACBITS;
            no->y = SHORT(mn->y) << FRACBITS;
            no->dx = SHORT(mn->dx) << FRACBITS;
            no->dy = SHORT(mn->dy) << FRACBITS;
            for (j = 0; j < 2; j++)
            {
                no->children[j] = LONG(mn->children[j]);
                for (k = 0; k < 4; k++)
                {
                    no->bbox[j][k] = SHORT(mn->bbox[j][k]) << FRACBITS;
                }
            }
        }
    }

    if (compressed && output)
    {
        Z_Free(output);
    }
    else
    {
        W_CacheLumpNum(lump, PU_CACHE);
    }
}
