package game77.ck1k_whole;
// Příliš žluťoučký kůň úpěl ďábelské ó - PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ Ó
// J:/77_JAVA/game77/ck1k_whole/Action.java

import game77.api.IAction;
import game77.api.IItem;
import game77.api.INamed;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static game77.ck1k_whole.ScenarioManager.*;


/*******************************************************************************
 * Instance třídy {@code Action}
 * mají na starosti interpretaci příkazů zadávaných uživatelem hrajícím hru.
 * Název spouštěné akce je první slovo zadávaného příkazu;
 * další slova pak jsou interpretována jako argumenty.
 * <p>
 * Lze ale definovat i akci, která odstartuje konverzaci
 * (např. s osobou přítomnou v místnosti) a tím přepne systém do režimu,
 * v němž se zadávané texty neinterpretují jako příkazy,
 * ale předávají se hře, nejvýše nastavují nějaký vnitřní stav,
 * a to až do chvíle, kdy bude rozhovor ukončen, a hra se přepne zpět
 * do režimu klasických příkazů.
 */
public   class Action
       extends ANamed
    implements IAction
{
//===== CLASS/STATIC ATTRIBUTES/FIELDS =========================================

    static private boolean isActive = false;

    static private final List<Action> ACTIONS = List.of(
        new Action("Jdi", Action::move,
            "Přesune Karkulku do zadaného sousedního prostoru."),
        new Action("Vezmi", Action::take,
            "Vezme zadaný předmět a vloží jej do košíku.\n"
          + "Předmět musí být v aktuálním prostoru, musí být přenositelný\n"
          + "a v košíku pro něj musí být volné místo."),
        new Action("Polož", Action::put,
            "Zadaný předmět z košíku položí v aktuálním prostoru."),
        new Action("?", Action::help,
            "Zobrazí seznam dostupných akcí spolu s jejich stručnými popisy."),
        new Action("Konec", (x) -> {
                stop();
                return anSTOP;
            },
            "Předčasně ukončí hru."),
        new Action("Probuď", Action::wakeUp,
            "Probudí zadaný probuditelný h-objekt."),
        new Action("Pozdrav", Action::greet,
            "Pozdraví přítomný, již probuzený objekt."),
        new Action("Popřej", Action::success,
            "Popřeje přítomnému pozdravenému objektu vše nejlepší\n" +
            "a úspěšně tím ukončí hru.")
    );

    /** Mapa typu (Jméno->Hodnota) testovaných a nastavovaných příznaků.
     *  Bude se inicializovat při každém startu hry. */
    private static final Map<String, Object> NAME_2_FLAG = new HashMap<>();

    /** Exportovaný pohled na mapu testovaných a nastavovaných příznaků,
     *  jenž neumožňuje hodnotu těchto příznaků změnit. */
    static final Map<String, Object> CONDITIONS =
                             Collections.unmodifiableMap(NAME_2_FLAG);

    /** Neměnná mapa (Jméno->Odkaz_na_test) testů, z nichž každý prověřuje
     *  jeden aspekt stavu hry potřebný k provedení některé pomocné akce. */
    static final Map<String, IAction.ITest> TESTS = Map.of(
            "argument_present",  Action::tstArgumentPresent,
            "argument_wakeable", Action::tstArgumentWakeable,
            "argument_sleeping", Action::tstArgumentSleeping,
            "wakeable_present",  Action::tstWakeablePresent,
            "waken_present",     Action::tstWakenPresent,
            "greeted_present",   Action::tstGreetedPresent
        );

    /** Mapa typu (Jméno->identifikátor) konvertující jméno ze scénáře
     *  na identifikátor používaný v programu. */
    private static final Map<String, String> NAME_2_ID = Map.of(
            inGRANDMA, "grandma",
            inWOLF,    "wolf" );

    /** Seznam názvů buditelných h-objektů. */
    private static final List<String> WAKEABLES = List.of(
            inGRANDMA.toLowerCase(),  inWOLF.toLowerCase());



//===== CLASS/STATIC METHODS ===================================================

    /***************************************************************************
     * Vrátí informaci o tom, je-li hra aktuálně spuštěná.
     *
     *
     * @return Je-li spuštěná, vrátí {@code true}, jinak vrátí {@code false}
     */
    static public boolean isActive()
    {
        return isActive;
    }


    /***************************************************************************
     * Ukončí běh hry a uvolní případné alokované prostředky.
     */
    static public void stop()
    {
        isActive = false;
    }


    /***************************************************************************
     * Zpracuje zadaný příkaz a vrátí text zprávy pro uživatele.
     *
     * @param command Zadávaný příkaz
     * @return Textová odpověď hry na zadaný příkaz
     */
    static public String executeCommand(String command)
    {
        command = command.strip();  //Odebrání počátečních a koncových mezer
        if (command.isEmpty()) {
            if (isActive) { return anEMPTY; }       //Příkaz hře, jež neběží
                    else  { return executeEmptyCommand(); }      //Start hry
        }
        else {
            if (isActive) { return executeStandardCommand(command); } //Běh hry
                     else { return anNOT_START; }    //Prázdný příkaz během hry
        }
    }


//===== CLASS (STATIC) PRIVATE AND AUXILIARY METHODS ===========================

    /***************************************************************************
     * Definuje reakci na prázdný příkaz.
     *
     * @return Uvítací text hry
     */
    private static String executeEmptyCommand()
    {
        isActive = true;
        initialize();
        return ScenarioManager.anWELCOME;
    }


    /***************************************************************************
     * Definuje reakci na neprázdný příkaz.
     *
     * @param command Slova zadávaného příkau
     * @return Odpověď hry
     */
    private static String executeStandardCommand(String command)
    {
        String[] words = command.split(" ");    //Rozdělení příkazu na slova
        Action  action = INamed.get(words[0], ACTIONS);     //Nalezení akce
        if (action == null) { return anUNKNOWN + command; }
                       else { return action.execute(words); }
    }


    /***************************************************************************
     * Inicializuje aplikaci při startu další hry.
     */
    private static void initialize()
    {
        World.get().initialize();
        Bag  .get().initialize();
        NAME_2_FLAG.put("grandma.sleeping", true);
        NAME_2_FLAG.put("grandma.greeted",  false);
        NAME_2_FLAG.put("wolf.sleeping", true);
        NAME_2_FLAG.put("wolf.greeted",  false);
    }


//===== STANDARDNÍ AKCE ========================================================

    /** Přesune Karkulku do zadaného sousedního prostoru. */
    private static String move(String[] arguments)
    {
        if (arguments.length < 2) { return anMOVE_WA; }     //Nezadaný cíl
        String       destName = arguments[1];       //Název cílového prostoru
        World           world = World.get();                //Správce světa
        Place    currentPlace = world.currentPlace();       //Aktuální prostor
        List<Place> neighbors = currentPlace.neighbors();   //Jeho sousedé
        Place     destination = INamed.get(destName, neighbors);//Cílový prostor
        if (destination == null) { return anBAD_NEIGHBOR + destName; } //======>
        world.setCurrentPlace(destination);         //Nastavení cílového prostoru
        return anGOTO + destination.description();  //Konstrukce odpovědi
    }


    /** Vezme zadaný předmět a vloží jej do košíku. */
    private static String take(String[] arguments)
    {
        if (arguments.length < 2) { return anTAKE_WA; }            //==========>
        World        world = World.get();       //Správce světa
        Place currentPlace = world.currentPlace();//Aktuální prostor
        String    itemName = arguments[1];      //Název zvedaného h-objektu
        Item          item = (Item)currentPlace.item(itemName); //Zvedaný h-obj
        if (item == null) { return anBAD_ITEM + itemName; }        //==========>
        Bag            bag = Bag.get();         //Batoh
        if (item.weight() > bag.capacity()) { return anUNMOVABLE + itemName; }
        if (bag.addItem(item)) {                //Pokus o vložení do batohu
            currentPlace.removeItem(item);      //Odebrání h-objektu z prostoru
            return anTAKE + item;               //Konstrukce odpovědi
        }
        return anBAG_FULL + item;               //Konstrukce odpovědi
    }


    /** Zadaný předmět z košíku položí v aktuálním prostoru. */
    private static String put(String[]  arguments)
    {
        if (arguments.length < 2) { return anPUT_DOWN_WA; }
        World        world = World.get();       //Správce světa
        Place currentPlace = world.currentPlace();//Aktuální prostor
        String    itemName = arguments[1];      //Název zvedaného h-objektu
        Bag            bag = Bag.get();         //Batoh
        Item          item = (Item)bag.item(itemName);  //Přesouvaný h-objekt
        if (item == null) { return anNOT_IN_BAG + itemName; }
        bag.removeItem(item);                   //Odebrání h-objektu z batohu
        currentPlace.addItem(item);             //... a jeho přesun do prostoru
        return anPUT + item;                    //Konstrukce odpovědi
    }


    /** Zobrazí cíl hry a seznam možných akcí spolu s jejich stručnými popisy.*/
    private static String help(String[] arguments)
    {
        StringBuilder result = new StringBuilder(SUBJECT)
                .append("\nProveditelné akce:")
                .append("\n==================\n");
        for (Action action : ACTIONS) {
            result.append(action.name()).append('\n')
                  .append(action.description()).append("\n\n");
        }
        return result.toString();
    }


//===== NESTANDARDNÍ AKCE ======================================================

    /** Probudí zadaný probuditelný h-objekt.*/
    private static String wakeUp(String[] arguments)
    {
        if (arguments.length < 2) { //Bylo zadáno, koho budit?
            return erNS1_WAKE_WA;   //Nebylo
        }
        if (! tstArgumentPresent(arguments)) { //Zadaný není v akt. prostoru
            return erNS1_WAKE_NO_HERE + arguments[1];
        }
        if (! tstArgumentWakeable(arguments)) {
            return erNS1_WAKE_WRONG_ARG + arguments[1];  //Není buditelný
        }
        if (! tstArgumentSleeping(arguments)) { //Spí zadaný objekt?
            return erNS1_WAKE_2NDa + arguments[1] + erNS1_WAKE_2NDb; //Nespí
        }
        Item argument = auxGetArgument(arguments);
        auxSetFlag(argument, "sleeping", false);  //Ulož, že je vzbuzený
        return anWAKE_UP + arguments[1];
    }


    /** Pozdraví přítomný, již probuzený objekt.*/
    private static String greet(String[] arguments)
    {
        if (! tstWakeablePresent(arguments)) { //Najdi buditelný v akt. prostoru
            return erMS0_GREET_NOBODY;         //Žádný buditelný objekt tu není
        }
        Item wakeable = auxGetWakeable();
        String   name = wakeable.name();    //Zjisti jméno do odpovědi
        if (! tstWakenPresent(arguments)) { //Zjisti, je-li už probuzený
            return erNS0_GREET_WAKEa + name + erNS0_GREET_WAKEb;
        }
        if (tstGreetedPresent(arguments)) { //Je už pozdravený?
            return erNS0_GREET_2NDa + name + erNS0_GREET_2NDb; //Zdraví podruhé
        }
        else {  //Nebyl jestě pozdravený - jdeme jej pozdravit
            auxSetFlag(wakeable, "greeted", true);  //Uloží, že už je
            return anGREETING + name;               //a oznámí odpověď
        }
    }


    /** Popřeje přítomnému pozdravenému objektu vše nejlepší
     *  a úspěšně tím ukončí hru. */
    private static String success(String[] arguments)
    {
        Item wakeable = auxGetWakeable();   //Zjisti buditelná objekt v akt. pr.
        if (wakeable == null) {  //Žádný buditelný v aktuálním prostoru není?
            return erSUCCESS_NOBODY;
        }
        if (tstGreetedPresent(arguments)) { //Je už pozdravený?
            isActive = false;               //Ano  popřejeme a končíme
            return anSUCCESSa + wakeable.name() + anSUCCESSb;
        }
        return erSUCCESS_GREETa + wakeable.name() + erSUCCESS_GREETb;
    }


//===== TESTY PROVĚŘUJÍCÍ VYKONATELNOST DANÉ POMOCNÉ AKCE ======================

    /** Metoda zjistí, je-li zadaný h-objekt v aktuálním prostoru. */
    private static boolean tstArgumentPresent(String[] arguments)
    {
        return (auxGetArgument(arguments) != null);
    }


    /** Metoda zjistí, patří-li zadaný argument mezi probuditelné. */
    private static boolean tstArgumentWakeable(String[] arguments)
    {
        Item     item = auxGetArgument(arguments); //Převede název na h-objekt
        Item wakeable = auxGetWakeable();  //Odkaz na přítomný buditelný či null
        return (item == wakeable);
    }


    /** Metoda zjistí, je-li zadaný argument spící. */
    private static boolean tstArgumentSleeping(String[] arguments)
    {
        Item        item = auxGetArgument(arguments);//Převede název na h-objekt
        return (boolean)(auxGetFlag(item, "sleeping"));
    }


    /** Metoda zjistí, je-li v aktuálním prostoru probuditelný objekt. */
    private static boolean tstWakeablePresent(String[] arguments)
    {
        return (auxGetWakeable() != null);
    }


    /** Metoda zjistí, je-li v aktuálním prostoru probuzený objekt. */
    private static boolean tstWakenPresent(String[] arguments)
    {
        Item  wakeable = auxGetWakeable(); //Převede název na buditelný h-objekt
        return (wakeable != null)
            && !(boolean) auxGetFlag(wakeable, "sleeping");
    }


//===== POMOCNÉ METODY ========================================================

    /** Metoda zjistí, je-li v aktuálním prostoru pozdravený objekt. */
    private static boolean tstGreetedPresent(String[] arguments)
    {
        Item  wakeable = auxGetWakeable();
        return (wakeable != null)
            && (boolean) auxGetFlag(wakeable, "greeted");
    }


    /** Vrátí h-objekt se zadaným názvem z aktuálního prostoru. */
    private static Item auxGetArgument(String[] arguments)
    {
        String   lowerName = arguments[1].toLowerCase();
        Place currentPlace = World.get().currentPlace();
        Item          item = currentPlace.item(lowerName);
        return item;
    }


    /** Vrátí odkaz na buditelný objekt v aktuálním prostoru nebo null. */
//    @SuppressWarnings("unchecked")
    private static Item auxGetWakeable() {
        Place currentPlace = World.get().currentPlace();
        for (IItem item : currentPlace.items()) {
            if (WAKEABLES.contains(item.name().toLowerCase())) {
                return (Item)item;
            }
        }
        return null;
    }


    /** Složí název nastavovaného příznaku a nastaví mu zadanou hodnotu. */
    private static Object auxGetFlag(Item item, String flagName)
    {
        String full_name = NAME_2_ID.get(item.name())
                         + '.' + flagName;
        boolean   result = (boolean)(NAME_2_FLAG.get(full_name));
        return result;
    }


    /** Složí název nastavovaného příznaku a nastaví mu zadanou hodnotu. */
    private static void auxSetFlag(Item item, String flagName, boolean value)
    {
        String full_name = NAME_2_ID.get(item.name())
                         + '.' + flagName;
        NAME_2_FLAG.put(full_name, value);
    }



//##############################################################################
//===== INSTANCE CONSTANTS (CONSTANT INSTANCE ATTRIBUTES/FIELDS) ===============

    /** Stručná charakteristika dané akce. */
    private final String description;

    /** Kód, který se má provést při zadání příslušného příkazu. */
    private final ICommand action;


//===== INSTANCE INITIALIZERS (CONSTRUCTORS) ===================================

    /***************************************************************************
     * Vytvoří novou akci se zadaným názvem, popisem a výkonným kódem.
     *
     * @param name          Název vytvářené akce
     * @param description   Popis akce pro nápovědu
     * @param action        Kód realizující danou akci
     */
    private Action(String name, ICommand action, String description)
    {
        super(name);
        this.action      = action;
        this.description = description;
    }


//===== INSTANCE GETTERS AND SETTERS ===========================================

    /***************************************************************************
     * Vrátí popis akce s vysvětlením její funkce,
     * významu jednotlivých parametrů
     * a možností (resp. účelu) použití dané akce.
     * Tento popis slouží jako nápověda k použití dané akce.
     *
     * @return Popis akce
     */
    @Override
    public String description()
    {
        return description;
    }


//===== INSTANCE REMAINING NON-PRIVATE METHODS =================================

    /***************************************************************************
     * Metoda realizující reakci hry na zadání daného příkazu.
     * Předávané pole je vždy neprázdné,
     * protože jeho nultý prvek je zadaný název vyvolaného příkazu.
     * Počet argumentů je závislý na konkrétním příkazu,
     * např. příkazy <i>konec</i> a <i>nápověda</i> nemají parametry,
     * příkazy <i>jdi</i> a <i>seber</i> očekávají zadání jednoho argumentu,
     * příkaz <i>použij</i> muže mít dva argumenty atd.
     *
     * @param arguments Zadané argumenty příkazu;
     *                  jejich celkový počet muže byt pro každý příkaz jiný,
     *                  ale nultý prvek vždy obsahuje název příkazu
     * @return Text zprávy vypsané po provedeni příkazu
     */
    @Override
    public String execute(String... arguments)
    {
        return action.execute(arguments);
    }
}
