def __init__(self, bot, config):
     EPBotImplant.__init__(self, bot, config)
     self.vote_rounds   = VoteRounds(self.config[self._config_keys[0]])
     self._used_regexes = {key: re.compile(rgx) for key, rgx in self._used_regexes.items()}
class VotingRounds(EPBotImplant):
    """ A voting round control implant.

    How to start a voting? Examples:
    'create poll' will start a dialogue. I will ask for:
    1) the topic of the poll,
    2) a code to reference the poll and
    3) the number of hours it will last.

    How to vote for a voting round with code *SDT1*? Examples:
    'vote SDT1 +1'
    'vote SDT1  0'
    'vote SDT1 -1'

    How to get the result for the round?
    'show results of voting round SDT1'

    How to close the round?
    'close voting round SDT1'
    'close poll SDT1'

    How to see the polls?
    'show open polls'
    'show all polls'

    The round codes must be unique during all the bot's life (or its database).
    The round codes have a maximum of 10 characters and should be all capital letters or numbers.
    Generally you can use the term 'voting round' as well as 'poll'.
    """

    _config_keys  = ['db_file_path']

    _used_regexes = dict([
        ('voting_transitives', r'(?P<action>\bcreate \b|\bclose \b|\bshow (?:\bthe \b)?(?:\bresult(?:s)? \b)?\b)(?:\bof \b)?(?:\bthe \b)?(?P<object>\bvoting(?:s)?(?:\b round\b)?\b|\bpoll\b|\b(?:\bopen\b|\ball\b)? poll(?:s)?\b|\b(?:\bopen\b|\ball\b)? voting round(?:s)?\b)(?: )?(?P<poll_code>[A-Z0-9]{1,10})?'),
        ('vote_value'        , r'(?P<action>\bvote\b|\bpoll\b) (?P<poll_code>[A-Z0-9]{1,10}) (?P<vote_value>\-1|0|1|\+1)'),
        ('vote_round_code'   , r'(?P<poll_code>[A-Z0-9]{1,10})'),
        ('poll_announcement' , r'\A(?:<@.*> )?(?:The poll).*(?:\(\*){1}(?P<poll_code>[A-Z0-9]{1,10})(?:\*\)){1}'),
    ])

    #_used_regexes = ['voting_transitives', 'vote_value', 'vote_round_code']

    _transitions  = { 'initial': 'idle',
                      # (event name, source state, destination state)
                      'events'   : [('vote_poll',           'idle',              'idle'),
                                    ('request_open_polls',  'idle',              'idle'),
                                    ('request_all_polls',   'idle',              'idle'),
                                    ('request_results',     'idle',              'idle'),

                                    # open poll transitions
                                    ('open_poll',           'idle',              'ask_topic'),

                                    ('receive_topic',       'ask_topic',         'ask_code'),
                                    ('topic_error',         'ask_topic',         'ask_topic'),

                                    ('receive_code',        'ask_code',          'ask_hours'),
                                    ('code_error',          'ask_code',          'ask_code'),

                                    ('receive_hours',       'ask_hours',         'ask_open_confirm'),
                                    ('hours_error',         'ask_hours',         'ask_hours'),

                                    ('confirm_open',        'ask_open_confirm',  'idle'),
                                    ('confirm_open_error',  'ask_open_confirm',  'ask_open_confirm'),

                                    # close poll transitions
                                    ('close_poll',          'idle',              'ask_close_confirm'),
                                    ('confirm_close',       'ask_close_confirm', 'idle'),
                                    ('confirm_close_error', 'ask_close_confirm', 'ask_close_confirm'),

                                    # cancel everything you are doing
                                    ('cancel',              '*',                 'idle')],

                      'callbacks': {'onvote_poll':            on_vote_poll,
                                    'onrequest_open_polls':   on_request_open_polls,
                                    'onrequest_all_polls':    on_request_all_polls,
                                    'onrequest_results':      on_request_results,

                                    #'onopen_poll':           on_create_poll,
                                    'onask_topic':            ask_topic,
                                    'onask_code':             ask_code,
                                    'onask_hours':            ask_hours,
                                    'onask_open_confirm':     confirm_poll_creation,

                                    'onbeforereceive_topic':  on_receive_topic,
                                    'onbeforereceive_code':   on_receive_code,
                                    'onbeforereceive_hours':  on_receive_hours,
                                    'onconfirm_open':         on_confirm_open,

                                    'onbeforeclose_poll':     on_receive_code,
                                    'onask_close_confirm':    confirm_poll_closing,
                                    'onconfirm_close':        on_confirm_close,

                                    'ontopic_error':          on_error,
                                    'oncode_error':           on_error,
                                    'onhours_error':          on_error,
                                    'onconfirm_open_error':   on_error,
                                    'onconfirm_close_error':  on_error,

                                    'oncancel':               on_cancel,
                                    }}

    def __init__(self, bot, config):
        EPBotImplant.__init__(self, bot, config)
        self.vote_rounds   = VoteRounds(self.config[self._config_keys[0]])
        self._used_regexes = {key: re.compile(rgx) for key, rgx in self._used_regexes.items()}

    def save_poll_timestamp(self, poll_match, timestamp):
        poll_code = get_group_from_match(poll_match, 'poll_code', '')
        try:
            self.vote_rounds.set_round_timestamp(poll_code, timestamp)
        except Exception as exc:
            #im.add_memo(e.user.name, reply_text=str(exc))
            #log.exception('Error `on_vote_poll`.')
            return str(exc)
        else:
            log.debug('Added {} to poll {}.'.format(timestamp, poll_code))
            return True

    def add_vote(self, poll_code, user_id, vote_value):
        try:
            self.vote_rounds.insert_vote(poll_code, user_id, vote_value)
        except Exception as exc:
            #im.add_memo(e.user.name, reply_text=str(exc))
            log.exception('Error `on_vote_poll`.')
            return str(exc)
        else:
            return 'Added vote by <@{}>: `{}` for poll {}.'.format(user_id, vote_value, poll_code)
            #im.add_memo(e.user.name, reply_text='Added `{}` vote by {} for round {}.'.format(vote_value, e.user.name,
            #                                                                                 poll_code))

    def match_event(self, state, msg):
        event = None

        if not state.isstate('idle') and msg == 'cancel':
            return 'cancel', None

        if state.isstate('idle'):
            tran_rgx = self._used_regexes['voting_transitives']
            vote_rgx = self._used_regexes['vote_value']

            # '(?P<action>\bcreate \b|\bclose \b|\bshow (?:\bresults of \b)?\b)' \
            match = tran_rgx.search(msg)
            if match:
                action    = get_group_from_match(match, 'action',    '').strip()
                object    = get_group_from_match(match, 'object',    '').strip()
                poll_code = get_group_from_match(match, 'poll_code', '').strip()

                if action.startswith('show') and (action.endswith('result') or action.endswith('results')):
                    if 'poll' in object or 'voting' in object:
                        return 'request_results', match

                elif action.startswith('show'):
                    if 'poll' in object or 'voting' in object:
                        if 'open' in object:
                            return 'request_open_polls', match
                        else:
                            return 'request_all_polls', match

                elif action.startswith('create'):
                    if 'poll' in object or 'voting' in object:
                        return 'open_poll', match

                elif action.startswith('close'):
                    if 'poll' in object or 'voting' in object:
                        if poll_code:
                            return 'close_poll', match

            # match a vote value
            match = vote_rgx.match(msg)
            if match:
                action = get_group_from_match(match, 'action', '')
                if action.startswith('vote'):
                    return 'vote_poll', match

        elif state.isstate('ask_topic'):
            if msg:
                return 'receive_topic', None
            else:
                return 'topic_error', None

        elif state.isstate('ask_code'):
            code_rgx = self._used_regexes['vote_round_code']
            match = code_rgx.match(msg)
            if match:
                return 'receive_code', match
            else:
                return 'code_error', None

        elif state.isstate('ask_hours'):
            match = re.compile(r"(?P<hours>[0-9]{1,3})").match(msg)
            if match:
                return 'receive_hours', match
            else:
                return 'hours_error', None

        elif state.isstate('ask_open_confirm'):
            if msg == 'yes' or msg == 'no':
                return 'confirm_open', None
            else:
                return 'confirm_open_error', None

        elif state.isstate('ask_close_confirm'):
            if msg == 'yes' or msg == 'no':
                return 'confirm_close', None
            else:
                return 'confirm_close_error', None

        else:
            event = None
            match = None

        return event, match

    def clear_vote_round_memo(self, user_name):
        self.pop_memo(user_name, 'round_code')
        self.pop_memo(user_name, 'topic')
        self.pop_memo(user_name, 'hours')

    @asyncio.coroutine
    def handle_message(self, msg):
        log.debug('Starting {}'.format(type(self).__name__))

        # check if this message is worth checking
        if not self.check_message(msg):
            return False

        # here follows the standard part of the process
        # cleanup the message text
        text = cleanup_message_text(msg['text'])

        # if not direct message, check if it starts with a mention to the bot name
        if not is_direct_channel(msg['channel']):
            # Mentions me?
            has_mention, text = has_initial_mentioning(text, self.rtm.user_id,
                                                       self.rtm.find_user(self.rtm.user_id).name)
            if not has_mention:
                return False

        # get who is talking to me, aka, user
        user = self.rtm.find_user(msg['user'])

        # get the state of the user
        user_state = self.user_state(user.name)
        current_state = user_state.current

        # process the text
        event, match  = self.match_event(user_state, text)
        if event is None:
            return False

        # trigger the state machine
        try:
            user_state.trigger(event, msg=text, user=user, implant=self, match=match)
        except Exception as exc:
            log.debug('Error triggering event `{}` in implant `{}` with message `{}`, '
                      'where user state was `{}`. Exception given: {}'.format(event, type(self).__name__,
                                                                              msg, current_state, str(exc)))
            return False
        else:
            # reply the user if there is any message for him
            reply_text = self.pop_reply_text(user.name)
            if reply_text:
                yield from self.bot.send_message(reply_text, msg['channel'], msg.get('user', None), mkdown=True)
                return True

    @asyncio.coroutine
    def handle_reaction_added(self, event):
        try:
            item_ts = event['item']['ts']
            poll = self.vote_rounds.find_vote_round_by_timestamp(item_ts)
        except MoreThanOneVoteRoundFound as mo:
            raise
        except:
            pass
        else:
            if poll:
                reaction = event['reaction']
                user_id = event.get('user', None)
                if reaction in slack_reaction_value:
                    value   = slack_reaction_value[reaction]
                    reply = self.add_vote(poll['code'], user_id, value)
                else:
                    reply = '<@{}> What do you mean by :{}: for poll {}?'.format(user_id, reaction, poll['code'])

                yield from self.bot.send_message(reply, event['item']['channel'], event.get('user', None),
                                                 add_mention=False)
                return True

    @asyncio.coroutine
    def handle_reaction_removed(self, event):
        #TODO
        pass

    @asyncio.coroutine
    def handle_my_own_reply(self, event):
        announce_rgx = self._used_regexes['poll_announcement']
        match = announce_rgx.match(event['text'])
        if match:
            self.save_poll_timestamp(poll_match=match, timestamp=event['ts'])
            return True