"""Variable Config Player (used for scoring and more)."""
from collections import namedtuple
from typing import Dict, List, Any

from mpf.core.config_player import ConfigPlayer
from mpf.core.machine import MachineController

VarBlock = namedtuple("VarBlock", ["priority", "context"])


class VariablePlayer(ConfigPlayer):

    """Posts events based on config."""

    config_file_section = 'variable_player'
    show_section = 'variables'

    __slots__ = ["blocks"]

    def __init__(self, machine: MachineController) -> None:
        """Initialise variable player."""
        super().__init__(machine)
        self.blocks = {}    # type: Dict[str, List[VarBlock]]

    @staticmethod
    def is_entry_valid_outside_mode(settings: dict) -> bool:
        """Return true if this entry may run without a game and player."""
        for event, setting in settings.items():
            del event
            if setting['action'] not in ("set_machine", "add_machine"):
                return False

        # true if only set_machine or add_machine are used
        return True

    def play(self, settings: dict, context: str, calling_context: str,
             priority: int = 0, **kwargs) -> None:
        """Variable name."""
        for var, s in settings.items():
            if var == "block":
                self.raise_config_error('Do not use "block" as variable name in variable_player.', 1, context=context)

            if s['action'] in ("add", "add_machine") and s['string']:
                self.raise_config_error('Cannot add two strings. Use action set or set_machine.', 3, context=context)

            if s['condition'] and not s['condition'].evaluate(kwargs):
                continue

            block_item = var + ":" + calling_context
            if self._is_blocked(block_item, context, priority):
                continue
            if s['block']:
                if block_item not in self.blocks:
                    self.blocks[block_item] = []
                if VarBlock(priority, context) not in self.blocks[block_item]:
                    self.blocks[block_item].append(VarBlock(priority, context))

            self._set_variable(var, s, kwargs, context)

    def _is_blocked(self, block_item: str, context: str,
                    priority: int) -> bool:
        if block_item not in self.blocks or not self.blocks[block_item]:
            return False
        priority_sorted = sorted(self.blocks[block_item], reverse=True)
        first_element = priority_sorted[0]
        return first_element.priority > priority and first_element.context != context

    def _set_variable(self, var: str, entry: dict, placeholder_parameters: dict, context) -> None:
        # evaluate placeholder
        if entry['float']:
            value = entry['float'].evaluate(placeholder_parameters)
        elif entry['int']:
            value = entry['int'].evaluate(placeholder_parameters)
        elif entry['string']:
            value = entry['string']
        else:
            value = None    # prevent type confusion
            self.raise_config_error("You need to either set float, int or string", 2, context=context)

        if entry['action'] == "add":
            if entry['player']:
                # specific player
                self.machine.game.player_list[entry['player'] - 1][var] += value
            else:
                # default to current player
                self.machine.game.player[var] += value
        elif entry['action'] == "set":
            if entry['player']:
                # specific player
                self.machine.game.player_list[entry['player'] - 1][var] = value
            else:
                # default to current player
                self.machine.game.player[var] = value
        elif entry['action'] == "add_machine":
            old_value = self.machine.get_machine_var(var)
            if old_value is None:
                old_value = 0
            self.machine.set_machine_var(var, old_value + value)
        elif entry['action'] == "set_machine":
            self.machine.set_machine_var(var, value)
        else:
            raise AssertionError("Invalid value: {}".format(entry))

    def clear_context(self, context: str) -> None:
        """Clear context."""
        for var in self.blocks:
            for entry, s in enumerate(self.blocks[var]):
                if s.context == context:
                    del self.blocks[var][entry]

    def validate_config_entry(self, settings: dict, name: str) -> dict:
        """Validate one entry of this player."""
        config = {}
        if not isinstance(settings, dict):
            raise AssertionError("Settings of variable_player {} should "
                                 "be a dict. But are: {}".format(name, settings))
        for var, s in settings.items():
            var_dict = self.machine.placeholder_manager.parse_conditional_template(var)
            value_dict = self._parse_config(s, name)
            value_dict["condition"] = var_dict["condition"]
            config[var_dict["name"]] = value_dict
        return config

    def get_express_config(self, value: Any) -> dict:
        """Parse express config."""
        if not isinstance(value, str):
            block = False
        else:
            try:
                value, block_str = value.split('|')
            except ValueError:
                block = False
            else:
                if block_str != "block":
                    raise AssertionError(
                        "Invalid action in variable_player entry: {}".format(value))
                block = True

        return {"int": value, "block": block}

    def get_list_config(self, value: Any):
        """Parse list."""
        raise AssertionError("Variable player does not support lists.")

@@ -51,16 +51,26 @@
 re).%22%22%22%0A
+import re%0A
 from col
@@ -4107,32 +4107,35 @@
         
-raise AssertionE
+self.raise_config_e
 rror(%22In
@@ -4164,16 +4164,36 @@
 t(entry)
+, 8, context=context
 )%0A%0A    d
@@ -4642,32 +4642,35 @@
         
-raise AssertionE
+self.raise_config_e
 rror(%22Se
@@ -4694,19 +4694,16 @@
 e_player
- %7B%7D
  should 
@@ -4737,16 +4737,19 @@
         
+ 
+  
 %22be a di
@@ -4776,14 +4776,8 @@
 mat(
-name, 
 sett
@@ -4781,16 +4781,33 @@
 ettings)
+, 5, context=name
 )%0A      
@@ -5091,16 +5091,294 @@
 ue_dict%0A
+            if not bool(re.match('%5E%5B0-9a-zA-Z_-%5D+$', var_dict%5B%22name%22%5D)):%0A                self.raise_config_error(%22Variable may only contain letters, numbers, dashes and underscores. %22%0A                                        %22Name: %7B%7D%22.format(var_dict%5B%22name%22%5D), 4, context=name)%0A
         
@@ -5774,54 +5774,32 @@
     
-raise AssertionError(%0A                        
+self.raise_config_error(
 %22Inv
@@ -5853,16 +5853,19 @@
 t(value)
+, 6
 )%0A      
@@ -6016,24 +6016,27 @@
     
-raise AssertionE
+self.raise_config_e
 rror
@@ -6077,10 +6077,13 @@
  lists.%22
+, 7
 )%0A

validate variable names. fix #1256