package com.brackeen.javagamebook.bsp2D;

import java.awt.Rectangle;
import java.awt.Point;
import java.util.List;
import com.brackeen.javagamebook.math3D.*;
import com.brackeen.javagamebook.graphics3D.texture.*;

/**
    Klasa BSPTree reprezentuje dwuwymiarowe drzewo dwójkowego podziału
    wielokątów. Obiekt klasy BSPTree jest budowany z wykorzystaniem
    klasy BSPTreeBuilder i może być przeglądany za pomocą klasy
    BSPTreeTraverser.
*/
public class BSPTree {

    /**
        Klasa Node reprezentuje węzeł drzewa. Wszystkie węzły potomne
        węzła znajdują się albo z przodu albo z tyłu podziału wprowadzanego
        przez węzeł.
    */
    public static class Node {
        public Node front;
        public Node back;
        public BSPLine partition;
        public List polygons;
    }


    /**
        Klasa Leaf reprezentuje liść drzewa. Liść nie wprowadza podziału,
        nie zawiera więc węzłów potomnych ani z przodu, ani z tyłu.
    */
    public static class Leaf extends Node {
        public float floorHeight;
        public float ceilHeight;
        public boolean isBack;
        public List portals;
        public Rectangle bounds;
    }

    private Node root;

    /**
        Tworzy nowy obiekt klasy BSPTree z określonym korzeniem.
    */
    public BSPTree(Node root) {
       this.root = root;
    }


    /**
        Zwraca korzeń drzewa.
    */
    public Node getRoot() {
        return root;
    }


    /**
        Oblicza dwuwymiarowy zakres dla wszystkich wielokątów w tym
        drzewie BSP. Zwraca prostokąt reprezentujący ten zakres.
    */
    public Rectangle calcBounds() {

        final Point min =
            new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
        final Point max =
            new Point(Integer.MIN_VALUE, Integer.MIN_VALUE);

        BSPTreeTraverser traverser = new BSPTreeTraverser();
        traverser.setListener(new BSPTreeTraverseListener() {

            public boolean visitPolygon(BSPPolygon poly,
                boolean isBack)
            {
                for (int i=0; i<poly.getNumVertices(); i++) {
                    Vector3D v = poly.getVertex(i);
                    int x = (int)Math.floor(v.x);
                    int y = (int)Math.floor(v.z);
                    min.x = Math.min(min.x, x);
                    max.x = Math.max(max.x, x);
                    min.y = Math.min(min.y, y);
                    max.y = Math.max(max.y, y);
                }

                return true;
            }
        });

        traverser.traverse(this);

        return new Rectangle(min.x, min.y,
            max.x - min.x, max.y - min.y);
    }


    /**
        Zwraca liść, do którego należy punkt o wskazanych współrzędnych x, z.
    */
    public Leaf getLeaf(float x, float z) {
        return getLeaf(root, x, z);
    }


    protected Leaf getLeaf(Node node, float x, float z) {
        if (node == null || node instanceof Leaf) {
            return (Leaf)node;
        }
        int side = node.partition.getSideThin(x, z);
        if (side == BSPLine.BACK) {
            return getLeaf(node.back, x, z);
        }
        else {
            return getLeaf(node.front, x, z);
        }
    }


    /**
        Zwraca węzeł pokrywający się z przekazaną linią podziału
        lub null, jeśli taki węzeł nie istnieje.
    */
    public Node getCollinearNode(BSPLine partition) {
        return getCollinearNode(root, partition);
    }


    protected Node getCollinearNode(Node node, BSPLine partition) {
        if (node == null || node instanceof Leaf) {
            return null;
        }
        int side = node.partition.getSide(partition);
        if (side == BSPLine.COLLINEAR) {
            return node;
        }
        if (side == BSPLine.FRONT) {
            return getCollinearNode(node.front, partition);
        }
        else if (side == BSPLine.BACK) {
            return getCollinearNode(node.back, partition);
        }
        else {
            // BSPLine.SPANNING: najpierw sprawdź z przodu, potem z tyłu
            Node front = getCollinearNode(node.front, partition);
            if (front != null) {
                return front;
            }
            else {
                return getCollinearNode(node.back, partition);
            }
        }
    }


    /**
        Zwraca liść z przodu przekazanego podziału.
    */
    public Leaf getFrontLeaf(BSPLine partition) {
        return getLeaf(root, partition, BSPLine.FRONT);
    }


    /**
        Zwraca liść z tyłu przekazanego podziału.
    */
    public Leaf getBackLeaf(BSPLine partition) {
        return getLeaf(root, partition, BSPLine.BACK);
    }


    protected Leaf getLeaf(Node node, BSPLine partition, int side)
    {
        if (node == null || node instanceof Leaf) {
            return (Leaf)node;
        }
        int segSide = node.partition.getSide(partition);
        if (segSide == BSPLine.COLLINEAR) {
            segSide = side;
        }
        if (segSide == BSPLine.FRONT) {
            return getLeaf(node.front, partition, side);
        }
        else if (segSide == BSPLine.BACK) {
            return getLeaf(node.back, partition, side);
        }
        else { // BSPLine.SPANNING
            // nie powinno się zdarzyć
            return null;
        }
    }


    /**
        Tworzy tekstury na powierzni wszystkich wielokątów reprezentowanych w drzewie BSP.
    */
    public void createSurfaces(final List lights) {
        BSPTreeTraverser traverser = new BSPTreeTraverser();
        traverser.setListener(new BSPTreeTraverseListener() {

            public boolean visitPolygon(BSPPolygon poly,
                boolean isBack)
            {
                Texture texture = poly.getTexture();
                if (texture instanceof ShadedTexture) {
                    ShadedSurface.createShadedSurface(poly,
                        (ShadedTexture)texture,
                        poly.getTextureBounds(), lights,
                        poly.getAmbientLightIntensity());
                }
                return true;
            }
        });

        traverser.traverse(this);
    }

}
