package com.brackeen.javagamebook.path;

import java.util.*;
import com.brackeen.javagamebook.game.GameObject;
import com.brackeen.javagamebook.math3D.Vector3D;
import com.brackeen.javagamebook.bsp2D.BSPTree;
import com.brackeen.javagamebook.bsp2D.Portal;

/**
    Klasa AStarSearchWithBSP jest podklasą interfejsu PathFinder,
    odnajdującą ścieżkę w drzewie BSP za pomocą algorytmu A*.
*/
public class AStarSearchWithBSP extends AStarSearch
    implements PathFinder
{

    /**
        Klasa LeafNode jest podklasą AStarNode reprezentującą
        położenie liścia w drzewie BSP. Wykorzystywana do 
        przechowywania węzłów startu i celu poszukiwanej drogi.
    */
    public static class LeafNode extends AStarNode {
        BSPTree.Leaf leaf;
        Vector3D location;

        public LeafNode(BSPTree.Leaf leaf, Vector3D location) {
            this.leaf = leaf;
            this.location = location;
        }

        public float getCost(AStarNode node) {
            return getEstimatedCost(node);
        }

        public float getEstimatedCost(AStarNode node) {
            float otherX;
            float otherZ;
            if (node instanceof Portal) {
                Portal other = (Portal)node;
                otherX = other.getMidPoint().x;
                otherZ = other.getMidPoint().z;
            }
            else {
                LeafNode other = (LeafNode)node;
                otherX = other.location.x;
                otherZ = other.location.z;
            }
            float dx = location.x - otherX;
            float dz = location.z - otherZ;
            return (float)Math.sqrt(dx * dx + dz * dz);
        }

        public List getNeighbors() {
            return leaf.portals;
        }
    }

    private BSPTree bspTree;


    /**
        Tworzy nowy obiekt przeszukiwania AStarSearchWithBSP 
        dla podanego drzewa BSP.
    */
    public AStarSearchWithBSP(BSPTree bspTree) {
        setBSPTree(bspTree);
    }

    public void setBSPTree(BSPTree bspTree) {
        this.bspTree = bspTree;
    }


    public Iterator find(GameObject a, GameObject b) {
        return find(a.getLocation(), b.getLocation());
    }


    public Iterator find(Vector3D start, Vector3D goal) {

        BSPTree.Leaf startLeaf = bspTree.getLeaf(start.x, start.z);
        BSPTree.Leaf goalLeaf = bspTree.getLeaf(goal.x, goal.z);

        // jeśli start i cel są w tym samym liściu drzewa, to nie ma
        // potrzeby korzystania z algorytmu A* 
        if (startLeaf == goalLeaf) {
            return Collections.singleton(goal).iterator();
        }

        AStarNode startNode = new LeafNode(startLeaf, start);
        AStarNode goalNode = new LeafNode(goalLeaf, goal);

        // tymczasowo dodaj właśnie przygotowany węzeł celu goalNode
        // do listy sąsiadów
        List goalNeighbors = goalNode.getNeighbors();
        for (int i=0; i<goalNeighbors.size(); i++) {
            Portal portal = (Portal)goalNeighbors.get(i);
            portal.addNeighbor(goalNode);
        }

        // wykonaj przeszukiwania z pomocą algorytmu A* 
        List path = super.findPath(startNode, goalNode);

        // usuń węzeł celu z listy sąsiadów
        for (int i=0; i<goalNeighbors.size(); i++) {
            Portal portal = (Portal)goalNeighbors.get(i);
            portal.removeNeighbor(goalNode);
        }

        return convertPath(path);
    }


    /**
        Konwertuje ścieżkę zbudowaną z węzłów AStarNodes na ścieżkę 
        zbudowaną z lokacji  zapisanych jako wektory Vector3D.
    */
    protected Iterator convertPath(List path) {
        if (path == null) {
            return null;
        }
        for (int i=0; i<path.size(); i++) {
            Object node = path.get(i);
            if (node instanceof Portal) {
                path.set(i, ((Portal)node).getMidPoint());
            }
            else {
                path.set(i, ((LeafNode)node).location);
            }
        }
        return Collections.unmodifiableList(path).iterator();
    }

    public String toString() {
        return "AStarSearchWithBSP";
    }
}
