package leigh.mud.engine;

import leigh.continuity.cmd.BasicCommandProcessor;
import leigh.continuity.cmd.CommandHandler;
import leigh.continuity.line.BasicLineHandler;
import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;
import leigh.mecha.term.ConsoleColors;
import leigh.mecha.util.StringAccumulator;
import leigh.mud.engine.cmd.WorldEditCommand;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;
import java.util.TreeSet;

/**
 * This class represents a player session. Each session is associated with a player object and a room object.
 * The primary command processing for player input, as well as routing for output messages, happens in this class.
 *
 * @author Alex Leigh
 */
public class MudSession extends BasicCommandProcessor {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(MudSession.class);
    private Player player;
    private Room location;
    private MudEngine engine;

    public MudSession(MudEngine engine, BasicLineHandler lineHandler) throws IOException {
        // TODO: Have this rotate out with different messages each time it is used.
        super(lineHandler);
        this.engine = engine;
        setUnknownResponse("What? I didn't understand you.");

        // TODO: Get this from telnet session via IAC interaction
        setConsoleWidth(80);

        logger.info("Session created.");
        // TODO: Actually load a player from somewhere based on un/pw
        player = new Player("A stranger");
        location = engine.getNewPlayerRoom();
        location.addPlayer(this);
        logger.info("New session. [loc: {}] [pc: {}]", location, player);

        addCommandHandler("LOOK", cmd -> location.look(cmd, this));
        addCommandHandler("COMMANDS", cmd -> {
            StringAccumulator sa = new StringAccumulator(", ");
            TreeSet<String> sortedKeys = new TreeSet<>(getHandlers().keySet());
            for (String cmdName : sortedKeys) {
                sa.append(cmdName);
            }

            // TODO: Color replacement support for optional color
            return ConsoleColors.BLUE + sa.toString() + ConsoleColors.RESET;
        });

        CommandHandler move = cmd -> {
            Direction d = Direction.getDirection(clean(cmd[0]));

            logger.debug("Desired direction: {}", d);

            Exit exit = location.getExits().get(d);

            if (exit == null) {
                return "Sorry, you can't go that way.";
            }

            // We allow for the concept the room is missing, although this should NEVER happen.
            Room dst = engine.getRoom(exit.getDestinationId());
            if (dst == null) {
                logger.warn("Missing room. [dst: {}] [src: {}]", exit.getDestinationId(), location.getId());
                return "Doesn't seem like anywhere to you...";
            }

            location.removePlayer(this);
            location = dst;
            dst.addPlayer(this);

            return dst.look(new String[]{"look"}, this);
        };

        addCommandHandler("NORTH", move);
        addCommandHandler("EAST", move);
        addCommandHandler("WEST", move);
        addCommandHandler("SOUTH", move);
        addCommandHandler("UP", move);
        addCommandHandler("DOWN", move);

        // TODO: This should be handled better, with some kind of alias / short command processing
        addCommandHandler("N", move);
        addCommandHandler("E", move);
        addCommandHandler("W", move);
        addCommandHandler("S", move);
        addCommandHandler("U", move);
        addCommandHandler("D", move);

        addCommandHandler("SAY", cmd -> location.say(cmd, this));
        addCommandHandler("HELP", cmd -> "Until there is HELP, try COMMANDS.");

        addCommandHandler("QUIT", cmd -> {

            close();
            // They are gone, so we have nothing to say.
            return null;
        });

        addCommandHandler("WE", new WorldEditCommand(this));
    }

    public Player getPlayer() {
        return player;
    }

    public void close() {
        logger.info("Session closing. {}", this);
        try {
            location.removePlayer(this);
        } catch (IOException e) {
            // Intentionally blank
        }
        super.close();
    }

    /**
     * Write an asynchronous message to the client. This custom implementation consumes any exceptions which occur.
     * If an exception does occur, <code>close()</code> is called on this session.
     */
    public void write(String str) {
        try {
            super.write(str);
        } catch (Exception e) {
            close();
        }
    }

    public MudEngine getEngine() {
        return engine;
    }

    public Room getLocation() {
        return location;
    }

    @Override
    public String getUnknownResponse() {
        ArrayList<String> responses = new ArrayList<>();
        responses.add("What? I didn't understand you.");
        responses.add("I'm sorry, I don't understand.");
        responses.add("I'm not sure I understand.");
        responses.add("Not sure what you are asking.");

        Random rand = new Random();
        return (responses.get(rand.nextInt(responses.size())));
    }
}
