#include "csg.h"

vec_t           g_BrushUnionThreshold = DEFAULT_BRUSH_UNION_THRESHOLD;

static Winding* NewWindingFromPlane(const brushhull_t* const hull, const int planenum)
{
    Winding*        winding;
    Winding*        front;
    Winding*        back;
    bface_t*        face;
    plane_t*        plane;

    plane = &g_mapplanes[planenum];
    winding = new Winding(plane->normal, plane->dist);

    for (face = hull->faces; face; face = face->next)
    {
        plane = &g_mapplanes[face->planenum];
        winding->Clip(plane->normal, plane->dist, &front, &back);
        delete winding;

        if (front)
        {
            delete front;
        }
        if (back)
        {
            winding = back;
        }
        else
        {
            Developer(DEVELOPER_LEVEL_ERROR, "NewFaceFromPlane returning NULL");
            return NULL;
        }
    }

    return winding;
}

static void     AddFaceToList(bface_t** head, bface_t* newface)
{
    hlassert(newface);
    hlassert(newface->w);
    if (!*head)
    {
        *head = newface;
        return;
    }
    else
    {
        bface_t*        node = *head;

        while (node->next)
        {
            node = node->next;
        }
        node->next = newface;
        newface->next = NULL;
    }
}

static int      NumberOfHullFaces(const brushhull_t* const hull)
{
    int             x;
    bface_t*        face;

    if (!hull->faces)
    {
        return 0;
    }

    for (x = 0, face = hull->faces; face; face = face->next, x++)
    {                                                  // counter
    }

    return x;
}

// Returns false if union of brushes is obviously zero
static void     AddPlaneToUnion(brushhull_t* hull, const int planenum)
{
    bool            need_new_face = false;

    bface_t*        new_face_list;

    bface_t*        face;
    bface_t*        next;

    plane_t*        split;
    Winding*        front;
    Winding*        back;

    new_face_list = NULL;

    next = NULL;

    hlassert(hull);

    if (!hull->faces)
    {
        return;
    }
    hlassert(hull->faces->w);

    for (face = hull->faces; face; face = next)
    {
        hlassert(face->w);
        next = face->next;

        // Duplicate plane, ignore
        if (face->planenum == planenum)
        {
            AddFaceToList(&new_face_list, CopyFace(face));
            continue;
        }

        split = &g_mapplanes[planenum];
        face->w->Clip(split->normal, split->dist, &front, &back);

        if (front)
        {
            delete front;
            need_new_face = true;

            if (back)
            {                                              // Intersected the face
                delete face->w;
                face->w = back;
                AddFaceToList(&new_face_list, CopyFace(face));
            }
        }
        else
        {
            // Completely missed it, back is identical to face->w so it is destroyed
            if (back)
            {
                delete back;
                AddFaceToList(&new_face_list, CopyFace(face));
            }
        }
        hlassert(face->w);
    }

    FreeFaceList(hull->faces);
    hull->faces = new_face_list;

    if (need_new_face && (NumberOfHullFaces(hull) > 2))
    {
        Winding*        new_winding = NewWindingFromPlane(hull, planenum);

        if (new_winding)
        {
            bface_t*        new_face = (bface_t*)Alloc(sizeof(bface_t));

            new_face->planenum = planenum;
            new_face->w = new_winding;

            new_face->next = hull->faces;
            hull->faces = new_face;
        }
    }
}

static vec_t    CalculateSolidVolume(const brushhull_t* const hull)
{
    // calculate polyhedron origin
    // subdivide face winding into triangles

    // for each face
    // calculate volume of triangle of face to origin
    // add subidivided volume chunk to total

    int             x = 0;
    vec_t           volume = 0.0;
    vec_t           inverse;
    vec3_t          midpoint = { 0.0, 0.0, 0.0 };

    bface_t*        face;

    for (face = hull->faces; face; face = face->next, x++)
    {
        vec3_t          facemid;

        face->w->getCenter(facemid);
        VectorAdd(midpoint, facemid, midpoint);
        Developer(DEVELOPER_LEVEL_MESSAGE, "Midpoint for face %d is %f %f %f\n", x, facemid[0], facemid[1], facemid[2]);
    }

    inverse = 1.0 / x;

    VectorScale(midpoint, inverse, midpoint);

    Developer(DEVELOPER_LEVEL_MESSAGE, "Midpoint for hull is %f %f %f\n", midpoint[0], midpoint[1], midpoint[2]);

    for (face = hull->faces; face; face = face->next, x++)
    {
        plane_t*        plane = &g_mapplanes[face->planenum];
        vec_t           area = face->w->getArea();
        vec_t           dist = DotProduct(plane->normal, midpoint);

        dist -= plane->dist;
        dist = fabs(dist);

        volume += area * dist / 3.0;
    }

    Developer(DEVELOPER_LEVEL_MESSAGE, "Volume for brush is %f\n", volume);

    return volume;
}

static void     DumpHullWindings(const brushhull_t* const hull)
{
    int             x = 0;
    bface_t*        face;

    for (face = hull->faces; face; face = face->next)
    {
        Developer(DEVELOPER_LEVEL_MEGASPAM, "Winding %d\n", x++);
        face->w->Print();
        Developer(DEVELOPER_LEVEL_MEGASPAM, "\n");
    }
}

static bool     isInvalidHull(const brushhull_t* const hull)
{
    int             x = 0;
    bface_t*        face;

    vec3_t          mins = { 99999.0, 99999.0, 99999.0 };
    vec3_t          maxs = { -99999.0, -99999.0, -99999.0 };

    for (face = hull->faces; face; face = face->next)
    {
        unsigned int    y;
        Winding*        winding = face->w;

        for (y = 0; y < winding->m_NumPoints; y++)
        {
            VectorCompareMinimum(mins, winding->m_Points[y], mins);
            VectorCompareMaximum(maxs, winding->m_Points[y], maxs);
        }
    }

    for (x = 0; x < 3; x++)
    {
        if ((mins[x] < (-BOGUS_RANGE / 2)) || (maxs[x] > (BOGUS_RANGE / 2)))
        {
            return true;
        }
    }
    return false;
}

void            CalculateBrushUnions(const int brushnum)
{
    int             bn, hull;
    brush_t*        b1;
    brush_t*        b2;
    brushhull_t*    bh1;
    brushhull_t*    bh2;
    entity_t*       e;

    b1 = &g_mapbrushes[brushnum];
    e = &g_entities[b1->entitynum];

    for (hull = 0; hull < 1 /* NUM_HULLS */ ; hull++)
    {
        bh1 = &b1->hulls[hull];
        if (!bh1->faces)                                   // Skip it if it is not in this hull
        {
            continue;
        }

        for (bn = brushnum + 1; bn < e->numbrushes; bn++)
        {                                                  // Only compare if b2 > b1, tests are communitive
            b2 = &g_mapbrushes[e->firstbrush + bn];
            bh2 = &b2->hulls[hull];

            if (!bh2->faces)                               // Skip it if it is not in this hull
            {
                continue;
            }
            if (b1->contents != b2->contents)
            {
                continue;                                  // different contents, ignore
            }

            Developer(DEVELOPER_LEVEL_SPAM, "Processing hull %d brush %d and brush %d\n", hull, brushnum, bn);

            {
                brushhull_t     union_hull;
                bface_t*        face;

                union_hull.bounds = bh1->bounds;

                union_hull.faces = CopyFaceList(bh1->faces);

                for (face = bh2->faces; face; face = face->next)
                {
                    AddPlaneToUnion(&union_hull, face->planenum);
                }

                // union was clipped away (no intersection)
                if (!union_hull.faces)
                {
                    continue;
                }

                if (g_developer >= DEVELOPER_LEVEL_MESSAGE)
                {
                    Log("\nUnion windings\n");
                    DumpHullWindings(&union_hull);

                    Log("\nBrush %d windings\n", brushnum);
                    DumpHullWindings(bh1);

                    Log("\nBrush %d windings\n", bn);
                    DumpHullWindings(bh2);
                }


                {
                    vec_t           volume_brush_1;
                    vec_t           volume_brush_2;
                    vec_t           volume_brush_union;
                    vec_t           volume_ratio_1;
                    vec_t           volume_ratio_2;

                    if (isInvalidHull(&union_hull))
                    {
                        FreeFaceList(union_hull.faces);
                        continue;
                    }

                    volume_brush_union = CalculateSolidVolume(&union_hull);
                    volume_brush_1 = CalculateSolidVolume(bh1);
                    volume_brush_2 = CalculateSolidVolume(bh2);

                    volume_ratio_1 = volume_brush_union / volume_brush_1;
                    volume_ratio_2 = volume_brush_union / volume_brush_2;

                    if ((volume_ratio_1 > g_BrushUnionThreshold) || (g_developer >= DEVELOPER_LEVEL_MESSAGE))
                    {
                        volume_ratio_1 *= 100.0;
                        Warning("Entity %d : Brush %d intersects with brush %d by %2.3f percent", b1->entitynum,
                                brushnum, bn, volume_ratio_1);
                    }
                    if ((volume_ratio_2 > g_BrushUnionThreshold) || (g_developer >= DEVELOPER_LEVEL_MESSAGE))
                    {
                        volume_ratio_2 *= 100.0;
                        Warning("Entity %d : Brush %d intersects with brush %d by %2.3f percent", b1->entitynum, bn,
                                brushnum, volume_ratio_2);
                    }
                }

                FreeFaceList(union_hull.faces);
            }
        }
    }
}
