"""
Sub-parser for the interactive house buying/selling dialog in BSD monop.

The dialog flow (buying):
  1. list_cur: "Kentucky ave. (R) (0) Indiana ave. (R) (0) Illinois ave. (R) (0)"
  2. "Houses will cost $N"
  3. "How many houses do you wish to buy for"
  4. Prompts: "Kentucky ave. (R) (0): " → user enters number
  5. Repeat for each property in monopoly
  6. "You asked for N houses for $X"
  7. "Is that ok? " → y/n

Selling is identical except:
  - "Houses will get you $N apiece" (step 2)
  - "How many houses do you wish to sell from" (step 3)
  - "You asked to sell N houses for $X" (step 6)

This parser tracks the prompts (which tell us property name + current houses)
and the user responses (how many to add/remove), then applies the changes
when confirmed.

Usage:
    hp = HouseParser()
    for each line:
        result = hp.feed(sender, message)
        if result is not None:
            # result is a dict: {"action": "buy"|"sell", "changes": {sq_id: new_count}}
            # or {"action": "cancel"} if rejected
"""

import re

# Lazy import to avoid circular dependency with monop_parser
_SQUARE_BY_NAME = None

def _get_square_by_name():
    global _SQUARE_BY_NAME
    if _SQUARE_BY_NAME is None:
        from monop_parser import SQUARE_BY_NAME
        _SQUARE_BY_NAME = SQUARE_BY_NAME
    return _SQUARE_BY_NAME


# Property prompt: "PropName (N): " where N is digit or H
PROMPT_RE = re.compile(r'^(.+?) \((\d|H)\):\s*$')

# list_cur line: "PropName (N) PropName (N) ..." (space-separated, trailing space)
LIST_CUR_RE = re.compile(r'^((?:.+? \(\d|H\) )+)\s*$')

HOUSES_COST_RE = re.compile(r'^Houses will cost \$(\d+)$')
HOUSES_GET_RE = re.compile(r'^Houses will get you \$(\d+) apiece$')
HOW_MANY_BUY_RE = re.compile(r'^How many houses do you wish to buy for$')
HOW_MANY_SELL_RE = re.compile(r'^How many houses do you wish to sell from$')
ASKED_BUY_RE = re.compile(r'^You asked for (\d+) houses for \$(\d+)$')
ASKED_SELL_RE = re.compile(r'^You asked to sell (\d+) houses for \$(\d+)$')
IS_THAT_OK_RE = re.compile(r'^Is that ok\?')
SPREAD_TOO_WIDE_RE = re.compile(r"^That makes the spread too wide")
TOO_MANY_RE = re.compile(r"^That's too many")


def _resolve_property_name(name):
    """Resolve a full property name from a prompt to a square ID.
    
    Prompts use the full name like 'Kentucky ave. (R)' — 
    this is the square name from the C board array.
    """
    return _get_square_by_name().get(name)


class HouseParser:
    """State machine for parsing house buy/sell dialogs."""
    
    def __init__(self):
        self.reset()
    
    def reset(self):
        """Reset to idle state."""
        self.state = "idle"       # idle, prompting, confirming
        self.action = None        # "buy" or "sell"
        self.price_per = 0        # cost per house (buy) or refund per house (sell)
        self.prompts = []         # list of (sq_id, current_houses) in prompt order
        self.responses = []       # list of int (user's count per prompt)
        self._current_prompt = None  # (sq_id, current_houses) waiting for response
    
    @property
    def active(self):
        """True if we're in the middle of a house dialog."""
        return self.state != "idle"
    
    def feed(self, sender, msg):
        """Feed a line. Returns a result dict when the dialog completes, else None.
        
        sender: "monop" for bot lines, anything else for player input
        msg: the message text (stripped of IRC prefix)
        
        Returns:
            None — dialog still in progress (or not in a dialog)
            {"action": "buy"|"sell", "changes": {sq_id: new_house_count}, "cost": int}
            {"action": "cancel"} — player rejected or dialog reset
        """
        msg = msg.strip()
        is_bot = (sender == "monop")
        
        if self.state == "idle":
            return self._handle_idle(msg, is_bot)
        elif self.state == "prompting":
            return self._handle_prompting(msg, is_bot)
        elif self.state == "confirming":
            return self._handle_confirming(msg, is_bot)
        return None
    
    def _handle_idle(self, msg, is_bot):
        if not is_bot:
            return None
        
        m = HOUSES_COST_RE.match(msg)
        if m:
            self.action = "buy"
            self.price_per = int(m.group(1))
            return None
        
        m = HOUSES_GET_RE.match(msg)
        if m:
            self.action = "sell"
            self.price_per = int(m.group(1))
            return None
        
        if HOW_MANY_BUY_RE.match(msg):
            self.state = "prompting"
            self.action = "buy"
            self.prompts = []
            self.responses = []
            return None
        
        if HOW_MANY_SELL_RE.match(msg):
            self.state = "prompting"
            self.action = "sell"
            self.prompts = []
            self.responses = []
            return None
        
        return None
    
    def _handle_prompting(self, msg, is_bot):
        if is_bot:
            # Property prompt from monop
            m = PROMPT_RE.match(msg)
            if m:
                name = m.group(1)
                houses_str = m.group(2)
                houses = 5 if houses_str == 'H' else int(houses_str)
                sq_id = _resolve_property_name(name)
                if sq_id is not None:
                    self._current_prompt = (sq_id, houses)
                    self.prompts.append((sq_id, houses))
                else:
                    # Can't resolve — still track position
                    self._current_prompt = (None, houses)
                    self.prompts.append((None, houses))
                
                # Hotel prompt during buying: C auto-sets input to 0, no user input
                if self.action == "buy" and houses == 5:
                    self.responses.append(0)
                    self._current_prompt = None
                # Zero houses prompt during selling: C auto-sets input to 0
                elif self.action == "sell" and houses == 0:
                    self.responses.append(0)
                    self._current_prompt = None
                return None
            
            # Confirmation line — all prompts done
            m = ASKED_BUY_RE.match(msg) or ASKED_SELL_RE.match(msg)
            if m:
                self.state = "confirming"
                self._current_prompt = None
                return None
            
            # Error: spread too wide or too many — retry
            if SPREAD_TOO_WIDE_RE.match(msg) or TOO_MANY_RE.match(msg):
                # C code retries — for "too many" it re-prompts same property,
                # for "spread too wide" it restarts all prompts
                if SPREAD_TOO_WIDE_RE.match(msg):
                    self.prompts = []
                    self.responses = []
                    self._current_prompt = None
                elif TOO_MANY_RE.match(msg):
                    # Re-prompt the same property (pop last response and prompt;
                    # C will re-output the prompt line which re-adds it)
                    if self.responses:
                        self.responses.pop()
                    if self.prompts:
                        self.prompts.pop()
                    self._current_prompt = None
                return None
            
            return None
        else:
            # Player response to a prompt
            if self._current_prompt is not None:
                try:
                    count = int(msg.lstrip('.'))
                    self.responses.append(count)
                except ValueError:
                    pass  # invalid input, C will re-prompt
                self._current_prompt = None
            return None
    
    def _handle_confirming(self, msg, is_bot):
        if is_bot:
            # "Is that ok?" — wait for player response
            return None
        
        # Player says yes or no
        answer = msg.lstrip('.').lower()
        if answer in ('y', 'yes'):
            result = self._compute_result()
            self.reset()
            return result
        else:
            # Rejected
            self.reset()
            return {"action": "cancel"}
    
    def _compute_result(self):
        """Compute the final house counts after confirmation."""
        changes = {}
        
        for i, (sq_id, current) in enumerate(self.prompts):
            if sq_id is None:
                continue
            if i >= len(self.responses):
                continue
            
            delta = self.responses[i]
            if delta == 0:
                continue
            
            if self.action == "buy":
                new_count = current + delta
            else:  # sell
                new_count = current - delta
            
            changes[sq_id] = max(0, min(5, new_count))
        
        total_houses = sum(self.responses[:len(self.prompts)])
        cost = total_houses * self.price_per
        
        return {
            "action": self.action,
            "changes": changes,
            "cost": cost,
        }
