# Copyright 2012, 2013 David Ewing
#
# This file is part of SagaBuilder.
#
# SagaBuilder is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# SagaBuilder is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with SagaBuilder.  If not, see <http://www.gnu.org/licenses/>.

"""A class to handle di(c)e rolls."""

import random
import re


class Dice:
    """A class to handles di(c)e rolls."""

    def __init__(self):
        # Values for each individual die rolled
        self.rolls = []
        self.total = None

    def roll(self, dice):
        """
        Roll the dice defined by 'dice' string.
        Returns total, list of rolls
        """
        self.rolls = []
        self.total = None

        expression = re.compile(r'(\D)')  # Match non-digits
        tokens = expression.split(dice)

        try:
            stack = self._roll_dice_in_tokens(tokens)
            stack = self._apply_modifiers(stack)
        except ValueError:
            raise ValueError("Dice format incorrect: " + dice)

        self.total = stack[0]

        return self.total, self.rolls

    def _roll_dice_in_tokens(self, tokens):
        """Roll all the dice in the provided token list"""
        # The stack is for evaluated tokens / scratch space
        stack = []
        for tok in tokens:
            if len(stack) == 0:
                stack.append(tok)
                continue

            if stack[-1:] == ['d']:
                # If the previous token was a 'd'
                # the current one should be a die size identifier
                try:
                    die_size = int(tok)
                except:
                    raise ValueError("format incorrect")

                number = stack[-2:-1][0]
                if number == '':
                    number = 1
                number = int(number)

                roll_total = self._roll_dice(number, die_size)

                # Remove the qty of dice and 'd' from the stack
                stack = stack[:-2]

                # replace the qty and size tokens for further processing
                stack.append(roll_total)
            else:
                stack.append(tok)
        return stack

    def _roll_dice(self, qty, die_size):
        """Roll the requested number and size of dice.
           @params: qty, die_size
                qty = number of dice to roll
                die_size = max value on die to roll
           @returns: total of all dice rolled
        """
        results = []
        for _ in range(1, qty + 1):
            roll = random.randint(1, die_size)
            results.append(roll)
            self.rolls.append(roll)
        return sum(results)

    def _apply_modifiers(self, tokens):
        """Apply modifiers to the rolled dice"""
        stack = []
        for tok in tokens:
            if len(stack) == 0:
                stack.append(tok)
                continue

            try:
                tok = int(tok)
            except ValueError:
                pass

            if isinstance(tok, int):
                expression = str(stack[-2:-1][0]) + stack[-1:][0] + str(tok)
                stack = stack[:-3]
                try:
                    result = eval(expression)
                except:
                    raise ValueError("format incorrect")
                stack.append(result)
            else:
                stack.append(tok)

        return stack
