#include "dukemap.h"

using namespace std;

namespace DukeNukem3D
{

    DukeMap::DukeMap(std::string FileName)
    {
        rawdata = NULL;

        std::ifstream file(FileName.c_str(), std::ios::binary | std::ios::ate);
        std::streamsize size = file.tellg();
        file.seekg(0, std::ios::beg);

        rawdata = new uint8_t[size];
        if(file.read((char*)rawdata, size))
        {
            pHeader = (MapHeader*)rawdata;

            pNumberOfSectors = (uint16_t*)(pHeader + 1);
            Sectors = (Sector*)(pNumberOfSectors + 1);

            pNumberOfWalls = (uint16_t*)(Sectors + *pNumberOfSectors);
            Walls = (Wall*)(pNumberOfWalls + 1);

            pNumberOfSprites = (uint16_t*)(Walls + *pNumberOfWalls);
            Sprites = (Sprite*)(pNumberOfSprites + 1);
        }
    }

    DukeMap::~DukeMap()
    {
        if(rawdata != NULL)
            delete[] this->rawdata;
    }

    Scene* DukeMap::ToScene()
    {
        Scene *result = new Scene();
        Mesh *mesh = new Mesh("Duke2Blender");
        int nextPoint = 0;

        map<Wall*, int> WallPointIndex;

        // ================================================================================ CREATE VERTICES
        for(DukeNukem3D::Sector* s = Sectors; s != (Sectors + *pNumberOfSectors); s++)
        {
            DukeNukem3D::Wall* FirstWall = Walls + s->FirstWall;
            int32_t ceilingZ = s->CeilingZPos;
            int32_t floorZ = s->FloorZPos;

            for(DukeNukem3D::Wall* w = FirstWall; ; w = Walls + w->NextWall)
            {
                WallPointIndex[w] = nextPoint;
                mesh->points.push_back(new Point3D(-w->LeftPointX, 0.1d * ceilingZ, w->LeftPointY));
                mesh->points.push_back(new Point3D(-w->LeftPointX, 0.1d * floorZ, w->LeftPointY));
                nextPoint += 2;

                if(w->NextWall == s->FirstWall)
                    break;
            }
        }


        // ================================================================================= CREATE FACES
        nextPoint = 0;
        for(DukeNukem3D::Sector* s = Sectors; s != (Sectors + *pNumberOfSectors); s++)
        {
            DukeNukem3D::Wall* FirstWall = Walls + s->FirstWall;


            Face *floorface = new Face();
            Face *ceilface = new Face();

            for(DukeNukem3D::Wall* w = FirstWall; ; w = Walls + w->NextWall)
            {
                ceilface->verts.push_back(mesh->points[nextPoint]);
                floorface->verts.push_back(mesh->points[nextPoint+1]);

                Wall* SuccWall = Walls + w->NextWall;
                int SuccWallPointIndex = WallPointIndex[SuccWall];


                if(
                  (
                      (w->WallOnTheOtherSideOfTheWall == (uint16_t)-1)                 // outside wall
                   &&
                      (w->SectorOnTheOtherSideOfTheWall == (uint16_t)-1)               // outside wall
                  )
            //       ||
            //          (WallPointIndex.find(Walls+w->WallOnTheOtherSideOfTheWall) == WallPointIndex.end())
                   ||
                      ((w->Flags & WALL_FLAG_MASKED) == WALL_FLAG_MASKED)        // masked wall
                )
                {
                    Face *face = new Face();
                    face->verts.push_back(mesh->points[nextPoint]);            // own ceiling
                    face->verts.push_back(mesh->points[nextPoint+1]);          // own floor
                    face->verts.push_back(mesh->points[SuccWallPointIndex+1]); // next wall-object's floor
                    face->verts.push_back(mesh->points[SuccWallPointIndex]);   // next wall-object's ceiling
                    mesh->faces.push_back(face);
                }
                else
                {
                    int NextSectorWallPointIndex = -1;
                    int NextSectorSuccWallPointIndex = -1;

                    if(WallPointIndex.find(Walls+w->WallOnTheOtherSideOfTheWall) != WallPointIndex.end())
                    {
                        Wall* NextSectorWall = Walls + w->WallOnTheOtherSideOfTheWall;
                        NextSectorWallPointIndex = WallPointIndex[NextSectorWall];
                        Wall* NextSectorSuccWall = Walls + NextSectorWall->NextWall;
                        NextSectorSuccWallPointIndex = WallPointIndex[NextSectorSuccWall];
                    }
                    else
                    {
                        Sector* NextSector = Sectors + w->SectorOnTheOtherSideOfTheWall;
                        NextSectorWallPointIndex = mesh->points.size();
                        NextSectorSuccWallPointIndex = NextSectorWallPointIndex + 2;
                        mesh->points.push_back(new Point3D(-SuccWall->LeftPointX, 0.1d * NextSector->CeilingZPos, SuccWall->LeftPointY));
                        mesh->points.push_back(new Point3D(-SuccWall->LeftPointX, 0.1d * NextSector->FloorZPos, SuccWall->LeftPointY));
                        mesh->points.push_back(new Point3D(-w->LeftPointX, 0.1d * NextSector->CeilingZPos, w->LeftPointY));
                        mesh->points.push_back(new Point3D(-w->LeftPointX, 0.1d * NextSector->FloorZPos, w->LeftPointY));
                    }

                    Face *face = new Face();
                    face->verts.push_back(mesh->points[nextPoint]);                      // own ceiling
                    face->verts.push_back(mesh->points[SuccWallPointIndex]);             // next ceiling
                    face->verts.push_back(mesh->points[NextSectorWallPointIndex]);       // opposite wall ceiling
                    face->verts.push_back(mesh->points[NextSectorSuccWallPointIndex]);   // opposite wall next ceiling
                    mesh->faces.push_back(face);

                    face = new Face();
                    face->verts.push_back(mesh->points[nextPoint+1]);                    // own floor
                    face->verts.push_back(mesh->points[SuccWallPointIndex+1]);           // next floor
                    face->verts.push_back(mesh->points[NextSectorWallPointIndex+1]);     // opposite wall floor
                    face->verts.push_back(mesh->points[NextSectorSuccWallPointIndex+1]); // opposite wall next floor
                    mesh->faces.push_back(face);
                }

                nextPoint += 2;
                if(w->NextWall == s->FirstWall)
                    break;
            }

            mesh->faces.push_back(floorface);
            //mesh->faces.push_back(ceilface);
            result->meshes.push_back(mesh);
        }

        return result;
    }

} // namespace DukeNukem3D
