package org.artisanlogiciel.games.maze.solve;

import org.artisanlogiciel.games.maze.LabyModel;
import org.artisanlogiciel.games.maze.MazeParams;
import org.artisanlogiciel.games.maze.Position;

import java.util.LinkedList;

public class SolvingModel
extends LabyModel
{

    public SolvingModel(MazeParams params) {
        super(params);
    }

    public SolvingModel(LabyModel model) {
        super(model);
    }

    /**
     * resolve this labyrinth using internal representation
     * initial (x,y) is exit, will return list of positions from start (0,0) to end (x,y)
     **/
    public LinkedList<DirectionPosition> resolve(int x, int y, MazeResolutionListener rlistener) {
        int width = getWidth();
        int height = getHeight();
        long safeguard = width * height;
        int newx = 0;
        int newy = 0;

        resetResolving();

        // list of alternate paths
        LinkedList<LinkedList<DirectionPosition>> altpath = new LinkedList<>();

        // list of positions from start to end
        LinkedList<DirectionPosition> backpath = new LinkedList<DirectionPosition>();
        // position that point to (x,y).
        DirectionPosition found = new DirectionPosition((short) 0, new Position(x, y));
        // entry
        Position entry = new Position(0, 0);

        while (!found.getPosition().equals(entry)) {
            Position last = found.getPosition();
            backpath.addFirst(found);
            found = null;

            // should find from all adjacent cells (directions) one that point to this.
            {
                // didx is index of four cell adjacent to this (x,y)
                for (int didx = 0; didx < 4; didx++) {
                    int delta = 0;
                    short direction = AllDirections[didx];
                    short reversedirection = getReverseDirection(didx);
                    short pointingdirection = DIRECTION;

                    if (isFlagSet(direction, POSITIVE)) {
                        delta = 1;
                        pointingdirection |= NEGATIVE;
                    } else {
                        delta = -1;
                        pointingdirection |= POSITIVE;
                    }

                    if (isFlagSet(direction, HORIZONTAL)) {
                        newx = last.getX() + delta;
                        newy = last.getY();
                        pointingdirection |= HORIZONTAL;
                    } else {
                        newy = last.getY() + delta;
                        newx = last.getX();
                        pointingdirection |= VERTICAL;
                    }

                    // internal GUARD.
                    if (!isFlagSet(reversedirection, pointingdirection)) {
                        System.out.println("[FATAL] Internal ERROR. Please check AllDirections order "
                                + (reversedirection & pointingdirection) + " " + pointingdirection);
                        return backpath;
                    }

                    Position p = new Position(newx, newy);
                    if ((newx >= 0) && (newy >= 0) && (newx < width) && (newy < height)) {
                        if (isFlagSet(getCell(p), reversedirection)) {
                            if (found != null) {
                                // there is already a potential solution in adjacent cell of last.
                                System.out.println("alternate " + p + " from " + last + "/" + safeguard);
                                // could be a unique parent of two paths...
                                // but search from other entry/exits than generated
                                // from
                                // ie entry(0,0) exit(width-1,height-1) not yet
                                // implemented.
                                {
                                    if (!isFlagSet(getCell(p), SOLVED)) {
                                        LinkedList<DirectionPosition> cp = new LinkedList<DirectionPosition>(backpath);
                                        DirectionPosition altfound = new DirectionPosition(reversedirection, p);
                                        cp.addFirst(altfound);
                                        altpath.addLast(cp);
                                        rlistener.notifySearchError("record alternate path " + p.toString());
                                    } else {
                                        // this was already solved, might be a loop.
                                        if (rlistener != null) {
                                            rlistener.notifySearchError("Loop " + last.toString() + " has two parents " + found.toString() + " "
                                                    + p.toString());
                                        }
                                        // continue;
                                    }
                                }
                            } else {
                                if (!isFlagSet(getCell(p), SOLVED)) {
                                    // this is first potential solution in adjacent cell of last.
                                    System.out.println("check " + p + " from " + last + "/" + safeguard);

                                    found = new DirectionPosition(reversedirection, p);
                                    if (rlistener != null) {
                                        rlistener.notifySearch(found);
                                    }
                                } else {
                                    // was already solved.
                                    System.out.println("already solved " + p + " from " + last + "/" + safeguard);
                                }
                            }
                            // support multiple pathes
                        } else {
                            System.out.println("not reachable " + p + " from " + last + "/" + safeguard);
                        }
                    } else {
                        System.out.println("p outofbounds " + p + "/" + safeguard);
                    }
                }
                if (found == null) {
                    if (!altpath.isEmpty()) {
                        // new possible backpath
                        backpath = altpath.removeFirst();
                        found = backpath.removeFirst();
                        if (rlistener != null) {
                            rlistener.notifySearchError("try alternate path " + found.toString());
                            rlistener.notifySearch(found);
                        }
                    }
                }
            }
            if (found == null) {
                if (!altpath.isEmpty()) {
                    rlistener.notifySearchError("No path found BUT ALTPATH !");
                }
                rlistener.notifySearchError("No path found !");
                break;
            }
            // System.out.println(found);
            if (isFlagSet(getCell(found.getPosition()), SOLVED)) {
                System.out.println("[INFO] position already solved" + found.toString() + " *length:" + backpath.size());
            } else {
                updateCell(found.getPosition(), SOLVED);
            }


            safeguard--;
            if (safeguard < 0) {
                rlistener.notifySearchError("Path too long ( or long overflow ) for width*height:" + (width * height) + " length:" + backpath.size());
                break;
            }
        }
        if (rlistener != null) {
            rlistener.notifyCompletion(backpath);
        }
        return backpath;
    }

}
