/*
Copyright (C) 2003-2006 Andrey Nazarov

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.

*/

//
// r_bsp.c -- map loading functions common for both renderers
//

#include "q_shared.h"
#include "com_public.h"
#include "qfiles.h"
#include "q_list.h"
#include "r_shared.h"

bspModel_t  r_world;

bspLeaf_t *Bsp_FindLeaf( vec3_t origin ) {
	bspNode_t *node;
	cplane_t *plane;
	vec_t dot;

	node = r_world.nodes;
	do {
		plane = node->plane;
		dot = DotProduct( plane->normal, origin ) - plane->dist;
		if( dot >= 0 ) {
			node = node->children[0];
		} else {
			node = node->children[1];
		}
	} while( node->plane );
	
	return ( bspLeaf_t * )node;
}

byte *Bsp_ClusterPVS( int clusterNum ) {
	if( !r_world.vis || clusterNum == -1 ) {
		return NULL;
	}

	return r_world.vis + clusterNum * r_world.rowsize;
}


/* ========================================================================= */

#define Bsp_Malloc( size )	sys.HunkAlloc( &r_world.pool, size )

static byte *loadData;

static void Bsp_DecompressVis( byte *src, byte *dst, uint32 rowsize ) {
	uint32 count;
	
	do {
		if( *src ) {
			*dst++ = *src++;
			rowsize--;
		} else {
			src++; count = *src++;
			if( count > rowsize ) {
				Com_WPrintf( "Bsp_DecompressVis: overrun\n" );
				count = rowsize;
			}
			rowsize -= count;
			while( count-- ) {
				*dst++ = 0;
			}
		}
	} while( rowsize );
}

static int Bsp_LoadVis( lump_t *lump ) {
	dvis_t *src_vis;
	byte *dst, *src;
	uint32 numClusters, rowsize;
	uint32 offset;
	int i;
	
	if( !lump->filelen ) {
		r_world.vis = NULL;
		r_world.numClusters = 0;
		return 0;
	}
	
	if( lump->filelen < sizeof( *src ) ) {
		Com_EPrintf( "LoadVis: bad lump size\n" );
		return -1;
	}

	src_vis = ( dvis_t * )( loadData + lump->fileofs );
	numClusters = LittleLong( src_vis->numclusters );
	if( !numClusters ) {
		r_world.vis = 0;
		r_world.numClusters = 0;
		return 0; /* it is OK to have map without vis */
	}
	if( numClusters > MAX_MAP_LEAFS/8 ) {
		Com_EPrintf( "LoadVis: bad number of clusters\n" );
		return -1;
	}
	
	rowsize = ( numClusters + 7 ) >> 3;
	r_world.numClusters = numClusters;
	r_world.rowsize = rowsize;
	r_world.vis = Bsp_Malloc( numClusters * rowsize );

	dst = r_world.vis;
	for( i = 0; i < numClusters; i++ ) {
		offset = LittleLong( src_vis->bitofs[i][DVIS_PVS] );
		if( offset < sizeof( *src_vis ) || offset >= lump->filelen ) {
			Com_EPrintf( "LoadVis: bad offset\n" );
            return -1;
		}
		
		src = ( byte * )src_vis + offset;
		Bsp_DecompressVis( src, dst, rowsize );
		dst += rowsize;
	}

	return 0;
}

static int Bsp_LoadVertices( lump_t *lump ) {
	int count;
	dvertex_t *src;
	dvertex_t *dst;
	int i;

	count = lump->filelen / sizeof( *src );
	if( lump->filelen % sizeof( *src ) ) {
		Com_EPrintf( "LoadVertices: bad lump size\n" );
		return -1;
	}

	r_world.vertices = Bsp_Malloc( ( count + EXTRA_VERTICES ) * sizeof( *dst ) );
	r_world.numVertices = count;

	src = ( dvertex_t * )( loadData + lump->fileofs );
	dst = r_world.vertices;
	for( i = 0; i < count; i++ ) {
		dst->point[0] = LittleFloat( src->point[0] );
		dst->point[1] = LittleFloat( src->point[1] );
		dst->point[2] = LittleFloat( src->point[2] );
		
		src++; dst++;	
	}

	return 0;
}

static int Bsp_LoadEdges( lump_t *lump ) {
	int count;
	dedge_t *src;
	dedge_t *dst;
	int i;

	count = lump->filelen / sizeof( *src );
	if( lump->filelen % sizeof( *src ) ) {
		Com_EPrintf( "LoadEdges: bad lump size\n" );
		return -1;
	}

	r_world.edges = Bsp_Malloc( ( count + EXTRA_EDGES ) * sizeof( *dst ) );
	r_world.numEdges = count;

	src = ( dedge_t * )( loadData + lump->fileofs );
	dst = r_world.edges;
	for( i = 0; i < count; i++ ) {
		dst->v[0] = LittleShort( src->v[0] );
		dst->v[1] = LittleShort( src->v[1] );
		
		src++; dst++;	
	}

	return 0;
}

static int Bsp_LoadSurfEdges( lump_t *lump ) {
	int count;
	int *src;
	int *dst;
	int i;

	count = lump->filelen / sizeof( *src );
	if( lump->filelen % sizeof( *src ) ) {
		Com_EPrintf( "LoadSurfEdges: bad lump size\n" );
		return -1;
	}

	r_world.surfEdges = Bsp_Malloc( ( count + EXTRA_SURFEDGES ) * sizeof( *dst ) );
	r_world.numSurfEdges = count;

	src = ( int * )( loadData + lump->fileofs );
	dst = r_world.surfEdges;
	for( i = 0; i < count; i++ ) {
		*dst++ = LittleLong( *src++ );	
	}

	return 0;
}

static int Bsp_LoadTexinfo( lump_t *lump ) {
	int count;
	int i;
	texinfo_t *src;
	bspTexinfo_t *dst, *tex;
    image_t *texture;
    char path[MAX_QPATH];
	int animNext;

	count = lump->filelen / sizeof( *src );
	if( lump->filelen % sizeof( *src ) ) {
		Com_EPrintf( "LoadTexinfo: bad lump size\n" );
		return -1;
	}

	r_world.numTexinfos = count;
	r_world.texinfos = Bsp_Malloc( sizeof( *dst ) * count );
	
	src = ( texinfo_t * )( loadData + lump->fileofs );
	dst = r_world.texinfos;
	for( i = 0; i < count; i++ ) {
		Q_strncpyz( dst->name, src->texture, sizeof( dst->name ) );
		dst->flags = LittleLong( src->flags );

		dst->axis[0][0] = LittleFloat( src->vecs[0][0] );
		dst->axis[0][1] = LittleFloat( src->vecs[0][1] );
		dst->axis[0][2] = LittleFloat( src->vecs[0][2] );

		dst->axis[1][0] = LittleFloat( src->vecs[1][0] );
		dst->axis[1][1] = LittleFloat( src->vecs[1][1] );
		dst->axis[1][2] = LittleFloat( src->vecs[1][2] );

		dst->offset[0] = LittleFloat( src->vecs[0][3] );
		dst->offset[1] = LittleFloat( src->vecs[1][3] );

#ifdef OPENGL_RENDERER
        upload_texinfo = dst;

        VectorNormalize2( dst->axis[0], dst->normalizedAxis[0] );
        VectorNormalize2( dst->axis[1], dst->normalizedAxis[1] );
#endif

		animNext = LittleLong( src->nexttexinfo );
		if( animNext > 0 ) {
			if( animNext >= count ) {
				Com_EPrintf( "Bsp_LoadTexinfo: bad anim chain\n" );
				return -1;
			}
			dst->animNext = r_world.texinfos + animNext;
		} else {
			dst->animNext = NULL;
		}

		Com_sprintf( path, sizeof( path ), "textures/%s.wal", dst->name );
        texture = R_FindImage( path, it_wall );
        if( texture ) {
            dst->image = texture;
        } else {
            dst->image = r_notexture;
        }

#ifdef OPENGL_RENDERER
        upload_texinfo = NULL;
#endif
		
		src++; dst++;
	}

	dst = r_world.texinfos;
	for( i = 0; i < count; i++ ) {
		dst->numFrames = 1;
		for( tex = dst->animNext; tex && tex != dst; tex = tex->animNext ) {
			dst->numFrames++;
		}
		dst++;
	}

	return 0;
}

static int Bsp_LoadFaces( lump_t *lump ) {
	int count;
	dface_t *src_face;
	bspSurface_t *dst_face;
	int i, j;
	uint32 texinfoNum;
    uint32 lightmapOffset;
	uint32 firstEdge, numEdges;
    uint32 planeNum;

	count = lump->filelen / sizeof( *src_face );
	if( lump->filelen % sizeof( *src_face ) ) {
		Com_EPrintf( "LoadFace: bad faces lump size\n" );
		return -1;
	}

	r_world.surfaces = Bsp_Malloc( ( count + EXTRA_SURFACES ) * sizeof( *dst_face ) );
	r_world.numSurfaces = count;

	src_face = ( dface_t * )( loadData + lump->fileofs );
	dst_face = r_world.surfaces;
	for( i = 0; i < count; i++ ) {
		firstEdge = LittleLong( src_face->firstedge );
		numEdges = LittleShort( src_face->numedges );
        if( numEdges < 3 || numEdges > MAX_MAP_SURFEDGES ) {
			Com_EPrintf( "LoadFace: bad surfedges\n" );
			return -1;
        }
		if( firstEdge + numEdges > r_world.numSurfEdges ) {
			Com_EPrintf( "LoadFace: surfedges out of bounds\n" );
			return -1;
		}

        planeNum = LittleShort( src_face->planenum );
        if( planeNum >= r_world.numPlanes ) {
            Com_EPrintf( "LoadFace: bad planenum\n" );
            return -1;
        }

		texinfoNum = LittleShort( src_face->texinfo );
		if( texinfoNum >= r_world.numTexinfos ) {
			Com_EPrintf( "LoadFace: bad texinfo\n" );
			return -1;
		}
        
        lightmapOffset = LittleLong( src_face->lightofs );
        if( lightmapOffset == ( uint32 )-1 || r_world.lightmapSize == 0 ) {
            dst_face->lightmap = NULL;
        } else {
            if( lightmapOffset >= r_world.lightmapSize ) {
                Com_EPrintf( "LoadFace: bad lightofs\n" );
                return -1;
            }
            dst_face->lightmap = r_world.lightmap + lightmapOffset;
        }

		dst_face->texinfo = r_world.texinfos + texinfoNum;
        j = LittleShort( src_face->side );
        dst_face->side = j & 1;
		dst_face->index = i;
        dst_face->type = DSURF_POLY;
        dst_face->plane = r_world.planes + planeNum;
        dst_face->firstSurfEdge = r_world.surfEdges + firstEdge;
        dst_face->numSurfEdges = numEdges;
		
#ifdef OPENGL_RENDERER
		if( GL_PostProcessSurface( dst_face ) ) {
            return -1;
        }
#endif

		src_face++; dst_face++;
	}
	

	return 0;
}

static int Bsp_LoadLeafFaces( lump_t *lump ) {
	int count;
	int i;
	uint32 faceNum;
	uint16 *src;
	bspSurface_t **dst;

	count = lump->filelen / sizeof( *src );
	if( lump->filelen % sizeof( *src ) ) {
		Com_EPrintf( "LoadLeafFaces: bad lump size\n" );
		return -1;
	}

	r_world.numLeafFaces = count;
	r_world.leafFaces = Bsp_Malloc( sizeof( *dst ) * count );
	
	src = ( uint16 * )( loadData + lump->fileofs );
	dst = r_world.leafFaces;
	for( i = 0; i < count; i++ ) {
		faceNum = LittleShort( *src );
		if( faceNum >= r_world.numSurfaces ) {
			Com_EPrintf( "LoadLeafFaces: bad face index\n" );
			return -1;
		}
		*dst = r_world.surfaces + faceNum;
		
		src++; dst++;
	}

	return 0;
}

static int Bsp_LoadLeafs( lump_t *lump ) {
	int count;
	int i;
	uint32 cluster, area;
	uint32 firstLeafFace;
	uint32 numLeafFaces;
	dleaf_t *src;
	bspLeaf_t *dst;

	count = lump->filelen / sizeof( *src );
	if( lump->filelen % sizeof( *src ) ) {
		Com_EPrintf( "LoadLeafs: bad lump size\n" );
		return -1;
	}

	r_world.numLeafs = count;
	r_world.leafs = Bsp_Malloc( sizeof( *dst ) * count );
	
	src = ( dleaf_t * )( loadData + lump->fileofs );
	dst = r_world.leafs;
	for( i = 0; i < count; i++ ) {
		dst->index = i;
		dst->plane = NULL;
		dst->parent = NULL;
		dst->visframe = -1;
	
		area = LittleShort( src->area );
		if( area >= MAX_MAP_AREAS ) {
			Com_EPrintf( "LoadLeafs: bad area num\n" );
			return -1;
		}
		dst->area = area;
		dst->contents = LittleLong( src->contents );

		cluster = LittleShort( src->cluster );
		if( cluster == ( uint16 )-1 || r_world.numClusters == 0 ) {
			dst->cluster = -1;
		} else {
            if( cluster >= r_world.numClusters ) {
			    Com_EPrintf( "LoadLeafs: bad cluster num\n" );
			    return -1;
		    }
    		dst->cluster = cluster;
        }
		
		firstLeafFace = LittleShort( src->firstleafface );
		numLeafFaces = LittleShort( src->numleaffaces );
		
		if( firstLeafFace + numLeafFaces > r_world.numLeafFaces ) {
			Com_EPrintf( "LoadLeafs: bad leafface\n" );
			return -1;
		}

		dst->firstLeafFace = r_world.leafFaces + firstLeafFace;
		dst->numLeafFaces = numLeafFaces;
		
		LSV( mins );
		LSV( maxs );
		
		src++; dst++;
	}

	return 0;
}

static int Bsp_LoadPlanes( lump_t *lump ) {
	int count;
	int i;
	dplane_t *src;
	cplane_t *dst;

	count = lump->filelen / sizeof( *src );
	if( lump->filelen % sizeof( *src ) ) {
		Com_EPrintf( "LoadPlanes: bad lump size\n" );
		return -1;
	}

	r_world.numPlanes = count;
	r_world.planes = Bsp_Malloc( sizeof( *dst ) * count );
	
	src = ( dplane_t * )( loadData + lump->fileofs );
	dst = r_world.planes;
	for( i = 0; i < count; i++ ) {
		LV( normal );
		LF( dist );
        SetPlaneType( dst );
        SetPlaneSignbits( dst );
		
		src++; dst++;
	}

	return 0;
}

static int Bsp_LoadNodes( lump_t *lump ) {
	int count;
	int i, j;
	uint32 planenum;
	uint32 child;
	dnode_t *src;
	bspNode_t *dst;
	uint32 firstFace, numFaces;

	count = lump->filelen / sizeof( *src );
	if( lump->filelen % sizeof( *src ) ) {
		Com_EPrintf( "LoadNodes: bad lump size\n" );
		return -1;
	}

	r_world.numNodes = count;
	r_world.nodes = Bsp_Malloc( sizeof( *dst ) * count );
	
	src = ( dnode_t * )( loadData + lump->fileofs );
	dst = r_world.nodes;
	for( i = 0; i < count; i++ ) {
		dst->index = i;
		dst->parent = NULL;
		dst->visframe = -1;
		
		/* load splitting plane index */
		planenum = LittleLong( src->planenum );
		if( planenum >= r_world.numPlanes ) {
			Com_EPrintf( "LoadNodes: bad planenum\n" );
			return -1;
		}
		dst->plane = r_world.planes + planenum;
		
		/* load children indices */
		for( j = 0; j < 2; j++ ) {
			child = LittleLong( src->children[j] );
			if( child & 0x80000000 ) {
				/* leaf index */
				child = ~child;
				if( child >= r_world.numLeafs ) {
					Com_EPrintf( "LoadNodes: bad leafnum\n" );
					return -1;
				}
				dst->children[j] = ( bspNode_t * )( r_world.leafs + child );
			} else {
				/* node index */
				if( child >= count ) {
					Com_EPrintf( "LoadNodes: bad nodenum\n" );
					return -1;
				}
				dst->children[j] = r_world.nodes + child;
			}
		}

		firstFace = LittleShort( src->firstface );
		numFaces = LittleShort( src->numfaces );

		if( firstFace + numFaces > r_world.numSurfaces ) {
			Com_EPrintf( "LoadNodes: bad faces\n" );
			return -1;
		}

		dst->firstFace = r_world.surfaces + firstFace;
		dst->numFaces = numFaces;
		
		LSV( mins );
		LSV( maxs );

		src++; dst++;
	}

	return 0;
}

#if 0
int Bsp_LoadEntityString( lump_t *lump ) {
	if( !lump->filelen ) {
        return 0;
    }

    r_world.entityString = Bsp_Malloc( lump->filelen + 1 );
    memcpy( r_world.entityString, loadData + lump->fileofs, lump->filelen );
    r_world.entityString[lump->filelen] = 0;
	
	return 0;
}
#endif

int Bsp_LoadLightMap( lump_t *lump ) {
	if( !lump->filelen ) {
        return 0;
    }

    r_world.lightmap = Bsp_Malloc( lump->filelen );
    memcpy( r_world.lightmap, loadData + lump->fileofs, lump->filelen );
    r_world.lightmapSize = lump->filelen;

    return 0;
}

static int Bsp_LoadSubmodels( lump_t *lump ) {
	int count;
	int i;
	dmodel_t *src;
	bspSubmodel_t *dst;
	uint32 firstFace, numFaces;
	uint32 headnode;

	count = lump->filelen / sizeof( *src );
	if( lump->filelen % sizeof( *src ) ) {
		Com_EPrintf( "LoadSubmodels: bad lump size\n" );
		return -1;
	}

	r_world.numSubmodels = count;
	r_world.submodels = Bsp_Malloc( sizeof( *dst ) * count );

	src = ( dmodel_t * )( loadData + lump->fileofs );
	dst = r_world.submodels;
	for( i = 0; i < count; i++ ) {
		firstFace = LittleLong( src->firstface );
		numFaces = LittleLong( src->numfaces );
		if( firstFace + numFaces > r_world.numSurfaces ) {
			Com_EPrintf( "LoadSubmodels: bad faces\n" );
			return -1;
		}

		headnode = LittleLong( src->headnode );
		if( headnode >= r_world.numNodes ) {
			/* FIXME: headnode may be garbage for some models */
			Com_DPrintf( "LoadSubmodels: bad headnode for model %d\n", i );
			dst->headnode = NULL;
		} else {
			dst->headnode = r_world.nodes + headnode;
		}

		dst->type = MODEL_BSP;
		dst->firstFace = r_world.surfaces + firstFace;
		dst->numFaces = numFaces;

		LV( mins );
		LV( maxs );
		LV( origin );

		dst->radius = RadiusFromBounds( dst->mins, dst->maxs );

		src++; dst++;
	}

	return 0;
}


static void Bsp_SetParent( bspNode_t *node ) {
	bspNode_t *child;
	
	while( node->plane ) {
		child = node->children[0];
		child->parent = node;
		Bsp_SetParent( child );
		
		child = node->children[1];
		child->parent = node;
		node = child;
	}

}

void Bsp_FreeWorld( void ) {
    if( r_world.name[0] ) {
        sys.HunkFree( &r_world.pool );
        memset( &r_world, 0, sizeof( r_world ) );
    }
}

qboolean Bsp_LoadWorld( const char *path ) {
	dheader_t header;
	lump_t *lump;
	int i;
    byte *data;
    int length;

    length = fs.LoadFileEx( path, ( void ** )&data, FS_FLAG_CACHE );
    if( !data ) {
        return qfalse;
    }

	if( length < sizeof( header ) ) {
		Com_EPrintf( "Bsp_Load: file length < header length\n" );
        goto fail;
	}
	
	/* byte swap and validate the header */
	header = *( dheader_t * )data;
	header.ident = LittleLong( header.ident );
	header.version = LittleLong( header.version );
	if( header.ident != IDBSPHEADER ) {
		Com_EPrintf( "Bsp_Load: not an IBSP file: %x != %x\n",
			header.ident, IDBSPHEADER );
        goto fail;
	}
	if( header.version != BSPVERSION ) {
		Com_EPrintf( "Bsp_Load: wrong version number: %u != %u\n",
			header.version, BSPVERSION );
        goto fail;
	}
	
	/* byte swap and validate lumps */
	lump = header.lumps;
	for( i = 0; i < HEADER_LUMPS; i++ ) {
		lump->fileofs = LittleLong( lump->fileofs );
		lump->filelen = LittleLong( lump->filelen );
		if( lump->fileofs + lump->filelen > length ) {
			Com_EPrintf( "Bsp_Load: lump #%d out of bounds\n", i );
            goto fail;
		}
		lump++;
	}

	loadData = data;

    
    /* reserve 16 MB of virtual memory */
    sys.HunkBegin( &r_world.pool, 0x1000000 );

#define BSP_LOAD( Func, Lump ) \
			if( Bsp_Load##Func( &header.lumps[LUMP_##Lump] ) ) { \
				sys.HunkFree( &r_world.pool ); \
                goto fail; \
			}

	BSP_LOAD( Vis, VISIBILITY )
	BSP_LOAD( Vertices, VERTEXES )
	BSP_LOAD( Edges, EDGES )
	BSP_LOAD( SurfEdges, SURFEDGES )
	BSP_LOAD( Texinfo, TEXINFO )
	BSP_LOAD( LightMap, LIGHTING )
	BSP_LOAD( Planes, PLANES )
	BSP_LOAD( Faces, FACES )
	BSP_LOAD( LeafFaces, LEAFFACES )
	BSP_LOAD( Leafs, LEAFS )
	BSP_LOAD( Nodes, NODES )
	BSP_LOAD( Submodels, MODELS )
//	BSP2_LOAD( EntityString, ENTITIES )
    
    fs.FreeFile( data );

    sys.HunkEnd( &r_world.pool );

    Bsp_SetParent( r_world.nodes );

    strcpy( r_world.name, path );

    return qtrue;

fail:
	fs.FreeFile( data );
	return qfalse;
}


