"""Earth locations — Bedroom, Front Porch, Front of House, Country Lane, Pub."""

from h2g2.engine.game_object import GameObject, Room, Flag, Direction, DirectExit, ConditionalExit, BlockedExit
from h2g2.engine.world import World
from h2g2.engine.state import GameState


# ---- Object action handlers ----

def phone_action(state: GameState) -> bool:
    out = state.output
    phone = state.world.get("PHONE")
    if state.prsa == "take":
        if phone.fset_q(Flag.TOUCHBIT):
            out.tell("You've already answered the phone.\n")
        else:
            phone.fset(Flag.TOUCHBIT)
            out.tell("You pick up the receiver. A dialling tone.\n")
        return True
    if state.prsa == "answer":
        phone.fset(Flag.TOUCHBIT)
        out.tell("You pick up the receiver. A dialling tone.\n")
        return True
    if state.prsa == "hang up":
        out.tell("You put down the receiver.\n")
        return True
    return False


def bed_action(state: GameState) -> bool:
    out = state.output
    bed = state.world.get("BED")
    protagonist = state.protagonist

    if state.prsa == "get out":
        if state.flags.get("headache"):
            protagonist.move_to(state.here)
            state.lying_down = False
            out.tell(
                "Very difficult, but you manage it. The room is still "
                "spinning. It dips and sways a little.\n"
            )
            return True
        protagonist.move_to(state.here)
        state.lying_down = False
        out.tell("You get out of bed.\n")
        return True

    if state.prsa == "look under":
        out.tell(
            "There's nothing there. Well, there are a few soiled "
            "handkerchiefs, a book you thought you'd lost, a couple of "
            "foreign coins, and something else which can't be fully "
            "described in a family game, but nothing you'd actually want.\n"
        )
        return True

    if state.prsa == "examine":
        out.tell("It's a bed. It's unmade and looks recently slept in.\n")
        return True

    return False


def gown_action(state: GameState) -> bool:
    out = state.output
    gown = state.world.get("GOWN")

    if state.prsa == "take" and state.prso is gown:
        if state.flags.get("headache"):
            gown.fclear(Flag.TRYTAKEBIT)
            gown.fclear(Flag.NDESCBIT)
            gown.move_to(state.protagonist)
            out.tell(
                "Luckily, this is large enough for you to get hold of. "
                "You notice something in the pocket.\n"
            )
            return True
        return False  # let default take handle it

    if state.prsa == "put on" and state.prso is gown:
        if not gown.is_held_by(state.protagonist):
            # Auto-take it first
            gown.move_to(state.protagonist)
        gown.fset(Flag.WORNBIT)
        out.tell("You are now wearing your gown.\n")
        return True

    if state.prsa == "examine":
        out.tell(
            "The dressing gown is faded and battered, and is clearly a "
            "garment which has seen better decades. It has a pocket which "
            "is "
        )
        if gown.fset_q(Flag.OPENBIT):
            out.tell("open")
        else:
            out.tell("closed")
        out.tell(", and a small loop at the back of the collar.\n")
        return True

    if state.prsa == "open":
        gown.fset(Flag.OPENBIT)
        visible = gown.contents_string()
        if visible:
            out.tell("Opening your gown reveals ")
            descs = [obj.a_desc() for obj in visible]
            out.tell(", ".join(descs[:-1]))
            if len(descs) > 1:
                out.tell(f", and {descs[-1]}")
            else:
                out.tell(descs[0])
            out.tell(".\n")
        else:
            out.tell("You open the gown pocket. It's empty.\n")
        return True

    if state.prsa == "look in":
        if not gown.fset_q(Flag.OPENBIT):
            gown.fset(Flag.OPENBIT)
        visible = gown.contents_string()
        if visible:
            out.tell("Opening your gown reveals ")
            descs = [obj.a_desc() for obj in visible]
            if len(descs) > 1:
                out.tell(", ".join(descs[:-1]))
                out.tell(f", and {descs[-1]}")
            else:
                out.tell(descs[0])
            out.tell(".\n")
        else:
            out.tell("The pocket is empty.\n")
        return True

    return False


def tablet_action(state: GameState) -> bool:
    out = state.output
    tablet = state.world.get("TABLET")

    if state.prsa in ("eat", "take", "drink", "swallow"):
        tablet.move_to(state.world.local_globals)  # consumed
        state.flags["headache"] = False
        state.score += 10
        out.tell(
            "You swallow the tablet. After a few seconds the room begins "
            "to calm down and behave in an orderly manner. Your terrible "
            "headache goes.\n"
        )
        # Unset TRYTAKEBIT on screwdriver and toothbrush
        screw = state.world.objects.get("SCREWDRIVER")
        tooth = state.world.objects.get("TOOTHBRUSH")
        if screw:
            screw.fclear(Flag.TRYTAKEBIT)
        if tooth:
            tooth.fclear(Flag.TRYTAKEBIT)
        return True

    if state.prsa == "examine":
        out.tell("It says \"Buffered Analgesic\" on the packet.\n")
        return True

    return False


def thing_action(state: GameState) -> bool:
    out = state.output
    if state.prsa == "examine":
        out.tell(
            'Apart from a label on the bottom saying "Made in Ibiza" it '
            "furnishes you with no clue as to its purpose, if indeed it "
            "has one. You are surprised to see it because you thought "
            "you'd thrown it away. Like most gifts from your aunt, it is "
            "extraordinarily dull.\n"
        )
        return True
    return False


def curtains_action(state: GameState) -> bool:
    out = state.output
    bulldozer = state.world.get("BULLDOZER")

    if state.prsa in ("open", "look in", "examine"):
        bulldozer.fclear(Flag.INVISIBLE)
        out.tell(
            "As you part your curtains you see that it is a bright "
            "morning, the sun is shining, the birds are singing, the "
            "meadows are blooming, and a large yellow bulldozer is "
            "advancing on your home.\n"
        )
        return True
    return False


def screwdriver_action(state: GameState) -> bool:
    out = state.output
    if state.prsa == "take" and state.flags.get("headache"):
        out.tell("It dances by you like a thing possessed.\n")
        return True
    if state.prsa == "examine":
        out.tell("It's a flathead screwdriver.\n")
        return True
    return False


def toothbrush_action(state: GameState) -> bool:
    out = state.output
    if state.prsa == "take" and state.flags.get("headache"):
        out.tell(
            "You lunge for it, but the room spins nauseatingly away. "
            "The floor gives you a light tap on the forehead.\n"
        )
        return True
    if state.prsa == "examine":
        out.tell("It's a perfectly normal toothbrush.\n")
        return True
    if state.prsa == "brush":
        out.tell("Your teeth are fine. It's the rest of you that's a wreck.\n")
        return True
    return False


def bulldozer_action(state: GameState) -> bool:
    out = state.output
    if state.prsa == "examine":
        if state.world.get("BULLDOZER").fset_q(Flag.INVISIBLE):
            out.tell("You can't see any bulldozer.\n")
        else:
            out.tell(
                "A large, My, a huge, My, a really huge yellow bulldozer.\n"
            )
        return True
    return False


def prosser_action(state: GameState) -> bool:
    out = state.output
    if state.prsa == "examine":
        if state.flags.get("prosser_in_mud"):
            out.tell(
                "Mr. Prosser is lying in the mud in front of the "
                "bulldozer.\n"
            )
        else:
            out.tell(
                "Mr. Prosser is a rather fat man. He seems agitated.\n"
            )
        return True
    if state.prsa in ("tell", "ask", "talk"):
        if state.flags.get("in_front_of_bulldozer"):
            out.tell(
                '"Look, Mr. Dent, the plans have been available in the '
                "planning office for the last nine months!\"\n"
            )
        else:
            out.tell(
                '"Come on, Mr. Dent, you can\'t win you know."\n'
            )
        return True
    return False


def ford_action(state: GameState) -> bool:
    out = state.output
    if state.prsa == "examine":
        out.tell(
            "He is not the sort of man you'd want to get stuck in a "
            "lift with, but at a pinch he'd do.\n"
        )
        return True
    if state.prsa in ("tell", "ask", "talk"):
        if state.here and state.here.id == "PUB":
            out.tell('"Drink up," says Ford, "you\'ve got three pints to get through."\n')
        elif state.here and state.here.id == "FRONT-OF-HOUSE":
            out.tell('"Arthur! Come to the pub! I\'ve got to tell you something!"\n')
        return True
    return False


def beer_action(state: GameState) -> bool:
    out = state.output
    beer = state.world.get("BEER")
    if state.prsa in ("drink", "eat"):
        state.flags["beer_counter"] = state.flags.get("beer_counter", 0) + 1
        state.score += 5
        beer_count = state.flags.get("beer_counter", 0)
        if beer_count >= 3:
            beer.move_to(state.world.local_globals)
            out.tell(
                "You finish the last of your beer. Ford then buys some "
                "peanuts and hands you something small and yellow and "
                "wriggly and shoves it in your ear.\n"
            )
        else:
            remaining = 3 - beer_count
            out.tell(
                f"You drink {'some' if beer_count == 1 else 'more'} "
                f"beer. {remaining} pint"
                f"{'s' if remaining != 1 else ''} left.\n"
            )
        return True
    if state.prsa == "examine":
        beer_count = state.flags.get("beer_counter", 0)
        out.tell(f"There {'are' if beer_count < 3 else 'is no'} beer"
                 f"{'s' if beer_count == 0 else ''} here.\n")
        return True
    return False


# ---- Room action handlers ----

def bedroom_action(state: GameState, rarg: str) -> bool:
    out = state.output

    if rarg == "M-LOOK":
        bed = state.world.get("BED")
        gown = state.world.get("GOWN")

        out.tell("The bedroom is a mess.\n")
        out.tell(
            "It is a small bedroom with a faded carpet and old wallpaper. "
            "There is a washbasin, a chair"
        )
        if gown.fset_q(Flag.NDESCBIT):
            out.tell(" with a tatty dressing gown slung over it")
        out.tell(
            ", and a window with the curtains drawn. Near the exit "
            "leading south is a phone.\n"
        )
        return True

    if rarg == "M-BEG":
        bed = state.world.get("BED")
        protagonist = state.protagonist
        # If player is in the bed, intercept certain actions
        if protagonist and protagonist.parent is bed:
            if state.prsa == "take" and state.prso:
                # Can't reach things from bed
                if state.prso.parent is not bed and not state.prso.is_held_by(protagonist):
                    out.tell("You can't reach it from the bed.")
                    if state.flags.get("headache"):
                        out.tell(" The effort almost kills you.")
                    out.tell("\n")
                    return True
        return False

    return False


def bedroom_exit(state: GameState) -> "Room | None":
    out = state.output
    door = state.world.get("BEDROOM-DOOR")

    # Must be out of bed first
    protagonist = state.protagonist
    bed = state.world.get("BED")
    if protagonist and protagonist.parent is bed:
        out.tell("You'll have to get out of bed first.\n")
        return None

    if not door.fset_q(Flag.OPENBIT):
        out.tell("The door is closed.\n")
        return None

    if state.flags.get("headache"):
        out.tell(
            "You miss the doorway by a good eighteen inches. The wall "
            "jostles you rather rudely.\n"
        )
        return None

    bulldozer = state.world.get("BULLDOZER")
    if bulldozer.fset_q(Flag.INVISIBLE):
        out.tell("You make your way down to the front porch.\n\n")
    else:
        out.tell("You rush down the stairs in panic.\n\n")

    return state.world.get_room("FRONT-PORCH")


def front_porch_action(state: GameState, rarg: str) -> bool:
    out = state.output
    if rarg == "M-LOOK":
        out.tell(
            "This is the enclosed front porch of your home. Your front "
            "garden lies to the south, and you can go back upstairs.\n"
        )
        return True
    return False


def clothes_exit(state: GameState) -> "Room | None":
    out = state.output
    gown = state.world.get("GOWN")
    if gown.fset_q(Flag.WORNBIT):
        return state.world.get_room("FRONT-OF-HOUSE")
    out.tell(
        "You can't leave the house in your current state. You'd be "
        "arrested for indecency.\n"
    )
    return None


def front_of_house_action(state: GameState, rarg: str) -> bool:
    out = state.output
    if rarg == "M-LOOK":
        if state.flags.get("house_demolished"):
            out.tell(
                "Where your home used to be there is now a pile of "
                "rubble, and through it runs a shiny new bypass.\n"
            )
        else:
            out.tell(
                "You can see your house from here. A large yellow "
                "bulldozer is approaching it.\n"
            )
            if state.flags.get("in_front_of_bulldozer"):
                out.tell("You are lying in the path of the bulldozer.\n")
        return True

    if rarg == "M-ENTER":
        # Ford arrives after a few turns
        pass

    return False


def house_enter(state: GameState) -> "Room | None":
    if state.flags.get("house_demolished"):
        state.output.tell("Your home is now a pile of rubble.\n")
        return None
    return state.world.get_room("FRONT-PORCH")


def country_lane_action(state: GameState, rarg: str) -> bool:
    out = state.output
    if rarg == "M-LOOK":
        out.tell(
            "You are in a pleasant country lane winding between the "
            "village and the pub. Your home lies to the north, and the "
            "pub is south.\n"
        )
        return True
    return False


def pub_action(state: GameState, rarg: str) -> bool:
    out = state.output
    if rarg == "M-LOOK":
        out.tell(
            "You are in the saloon bar of the Horse and Groom. "
            "The pub is pleasant and cheerful. The exit is to the north.\n"
        )
        return True
    if rarg == "M-ENTER":
        ford = state.world.get("FORD")
        beer = state.world.get("BEER")
        if not state.flags.get("ford_arrived"):
            state.flags["ford_arrived"] = True
            ford.move_to(state.here)
            beer.move_to(state.here)
            out.tell(
                '\nFord Prefect bursts in. "Arthur! Thank God you\'re here! '
                "I've got to tell you the most important thing you've ever "
                'heard!"\n'
                "He buys you three pints of beer.\n"
            )
    return False


# ---- Registration ----

def register(world: World) -> None:
    """Create all Earth rooms and objects."""

    # ---- Objects (not in rooms yet) ----

    bedroom_door = world.register(GameObject(
        "BEDROOM-DOOR", desc="door",
        synonyms=["door"],
        flags={Flag.DOORBIT, Flag.OPENBIT, Flag.NDESCBIT},
    ))
    bedroom_door.move_to(world.local_globals)

    bulldozer = world.register(GameObject(
        "BULLDOZER", desc="bulldozer",
        synonyms=["bulldozer", "tractor"],
        adjectives=["large", "yellow", "big"],
        flags={Flag.INVISIBLE, Flag.NDESCBIT},
        action=bulldozer_action,
    ))
    bulldozer.move_to(world.local_globals)

    prosser = world.register(GameObject(
        "PROSSER", desc="Mr. Prosser",
        synonyms=["prosser", "man"],
        adjectives=["fat", "mr"],
        flags={Flag.NARTICLEBIT, Flag.ACTORBIT, Flag.NDESCBIT},
        action=prosser_action,
    ))

    ford = world.register(GameObject(
        "FORD", desc="Ford Prefect",
        synonyms=["ford", "prefect"],
        adjectives=["ford"],
        flags={Flag.NARTICLEBIT, Flag.ACTORBIT, Flag.NDESCBIT},
        action=ford_action,
    ))

    window = world.register(GameObject(
        "WINDOW", desc="window",
        synonyms=["window"],
        flags={Flag.NDESCBIT},
    ))
    window.move_to(world.local_globals)

    stairs = world.register(GameObject(
        "STAIRS", desc="stairs",
        synonyms=["stairs", "staircase", "stairway"],
        flags={Flag.NDESCBIT},
    ))
    stairs.move_to(world.local_globals)

    # ---- BEDROOM ----

    bedroom = world.register(Room(
        "BEDROOM", desc="Bedroom",
        flags={Flag.RLANDBIT},
        action=bedroom_action,
        global_objects=[
            world.get("BEDROOM-DOOR"), bulldozer, window, stairs,
        ],
    ))

    bed = world.register(GameObject(
        "BED", desc="bed",
        synonyms=["bed"],
        flags={Flag.VEHBIT, Flag.CONTBIT, Flag.SURFACEBIT,
               Flag.SEARCHBIT, Flag.OPENBIT, Flag.NDESCBIT},
        action=bed_action,
    ))
    bed.move_to(bedroom)

    phone = world.register(GameObject(
        "PHONE", desc="telephone",
        synonyms=["phone", "telephone", "receiver"],
        flags={Flag.NDESCBIT, Flag.TAKEBIT, Flag.TRYTAKEBIT},
        action=phone_action,
    ))
    phone.move_to(bedroom)

    gown = world.register(GameObject(
        "GOWN", desc="your gown",
        ldesc="Your gown is here.",
        synonyms=["gown", "pocket", "robe"],
        adjectives=["my", "your", "dressing", "tatty", "faded", "battered"],
        flags={Flag.WEARBIT, Flag.TRYTAKEBIT, Flag.TAKEBIT, Flag.CONTBIT,
               Flag.NDESCBIT, Flag.NARTICLEBIT, Flag.SEARCHBIT},
        size=15, capacity=14,
        action=gown_action,
    ))
    gown.move_to(bedroom)

    curtains = world.register(GameObject(
        "CURTAINS", desc="your curtains",
        synonyms=["curtains", "curtain", "shade", "shades"],
        adjectives=["your"],
        flags={Flag.NDESCBIT, Flag.NARTICLEBIT},
        action=curtains_action,
    ))
    curtains.move_to(bedroom)

    # Objects inside the gown
    thing = world.register(GameObject(
        "THING", desc="thing your aunt gave you which you don't know what it is",
        synonyms=["thing", "gift"],
        adjectives=["aunt's"],
        flags={Flag.TAKEBIT, Flag.CONTBIT, Flag.SEARCHBIT, Flag.OPENBIT},
        size=6, capacity=90,
        action=thing_action,
    ))
    thing.move_to(gown)

    fluff = world.register(GameObject(
        "POCKET-FLUFF", desc="pocket fluff",
        synonyms=["fluff", "lint"],
        adjectives=["pocket"],
        flags={Flag.TAKEBIT, Flag.NARTICLEBIT},
        size=1,
    ))
    fluff.move_to(gown)

    tablet = world.register(GameObject(
        "TABLET", desc="buffered analgesic",
        synonyms=["analgesic", "tablet", "aspirin", "pill"],
        adjectives=["large", "buffered"],
        flags={Flag.TAKEBIT},
        size=2,
        action=tablet_action,
    ))
    tablet.move_to(gown)

    screwdriver = world.register(GameObject(
        "SCREWDRIVER", desc="flathead screwdriver",
        synonyms=["screwdriver", "tool", "tools"],
        adjectives=["flathead", "flat"],
        flags={Flag.TAKEBIT, Flag.TRYTAKEBIT, Flag.TOOLBIT},
        size=3,
        action=screwdriver_action,
    ))
    screwdriver.move_to(bedroom)

    toothbrush = world.register(GameObject(
        "TOOTHBRUSH", desc="toothbrush",
        synonyms=["toothbrush", "brush"],
        adjectives=["tooth", "my"],
        flags={Flag.TAKEBIT, Flag.TRYTAKEBIT, Flag.TOOLBIT},
        size=3,
        action=toothbrush_action,
    ))
    toothbrush.move_to(bedroom)

    sink = world.register(GameObject(
        "SINK", desc="washbasin",
        synonyms=["basin", "washbasin", "sink"],
        adjectives=["wash"],
        flags={Flag.NDESCBIT, Flag.NARTICLEBIT},
    ))
    sink.move_to(bedroom)

    # Start player in bed
    world.protagonist.move_to(bed)

    # ---- FRONT PORCH ----

    front_porch = world.register(Room(
        "FRONT-PORCH", desc="Front Porch",
        flags={Flag.RLANDBIT, Flag.ONBIT},
        action=front_porch_action,
        global_objects=[
            world.get("BEDROOM-DOOR"), bulldozer, window, stairs,
        ],
    ))

    mail = world.register(GameObject(
        "MAIL", desc="some junk mail",
        synonyms=["mail", "junk", "letter", "letters"],
        flags={Flag.TAKEBIT, Flag.NARTICLEBIT},
        text="Strewn among the usual junk mail is a notice from the local "
             "council about a proposed demolition of your house.",
    ))
    mail.move_to(front_porch)

    # ---- FRONT OF HOUSE ----

    front_of_house = world.register(Room(
        "FRONT-OF-HOUSE", desc="Front of House",
        flags={Flag.RLANDBIT, Flag.ONBIT, Flag.OUTSIDEBIT},
        action=front_of_house_action,
        global_objects=[bulldozer, prosser],
    ))
    prosser.move_to(front_of_house)

    # ---- COUNTRY LANE ----

    country_lane = world.register(Room(
        "COUNTRY-LANE", desc="Country Lane",
        flags={Flag.RLANDBIT, Flag.ONBIT, Flag.OUTSIDEBIT},
        action=country_lane_action,
    ))

    # ---- PUB ----

    pub = world.register(Room(
        "PUB", desc="Pub",
        flags={Flag.RLANDBIT, Flag.ONBIT},
        action=pub_action,
    ))

    beer = world.register(GameObject(
        "BEER", desc="beer",
        synonyms=["beer", "pint", "pints", "lager", "ale"],
        adjectives=["three"],
        flags={Flag.DRINKBIT, Flag.NDESCBIT},
        action=beer_action,
    ))

    # ---- Connect rooms ----

    bedroom.exits = {
        Direction.SOUTH: ConditionalExit(bedroom_exit),
        Direction.OUT: ConditionalExit(bedroom_exit),
        Direction.DOWN: ConditionalExit(bedroom_exit),
    }

    front_porch.exits = {
        Direction.UP: DirectExit(bedroom),
        Direction.NORTH: DirectExit(bedroom),
        Direction.SOUTH: ConditionalExit(clothes_exit),
        Direction.OUT: ConditionalExit(clothes_exit),
    }

    front_of_house.exits = {
        Direction.NORTH: ConditionalExit(house_enter),
        Direction.SOUTH: DirectExit(country_lane),
        Direction.IN: ConditionalExit(house_enter),
    }

    country_lane.exits = {
        Direction.NORTH: DirectExit(front_of_house),
        Direction.SOUTH: DirectExit(pub),
    }

    pub.exits = {
        Direction.NORTH: DirectExit(country_lane),
        Direction.OUT: DirectExit(country_lane),
    }
