""" Classes for making complicated / interdependent sequences of
    FCP requests using state machine logic.

    Copyright (C) 2009 Darrell Karbott

    This library 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 2.0 of the License, or (at your option) any later version.

    This library 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 this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

    Author: djk@isFiaD04zgAgnrEC5XJt1i4IE7AkNPqhBG5bONi6Yks
"""


# REDFLAG: move this into requestqueue?

import os

from fcpconnection import SUCCESS_MSGS
from requestqueue import QueueableRequest

# Move this to fcpconnection?
def delete_client_file(client):
    """ Delete the file in client.inf_params.file_name. """
    if client.in_params is None:
        return
    if client.in_params.file_name is None:
        return
    if not os.path.exists(client.in_params.file_name):
        return
    assert client.is_finished()
    os.remove(client.in_params.file_name)
# allow tuples, lists?
def require_state(state, state_name):
    """ Raise if state.name != state_name. """
    if state is None or state.name != state_name:
        raise Exception("Illegal State")

# Halting when connection drops?
class StateMachine:
    """ CS101 state machine treatment. """
    def __init__(self):
        # name -> State
        self.states = {}
        self.current_state = None # Subclass should set.
        self.transition_callback = lambda old_state, new_state: None

    def get_state(self, state_name):
        """ Get a state object by name. """
        return self.states[state_name]

    def require_state(self, state_name):
        """ Assert that the current state has the name state_name """
        require_state(self.current_state, state_name)

    def transition(self, to_name):
        """ Transition to the state to_name. """
        new_state = self.states[to_name]
        assert new_state.name == to_name
        old_state = self.current_state
        old_state.leave(new_state) # Shouldn't change state.
        assert self.current_state == old_state
        self.current_state = new_state # Hmmm... order
        self.transition_callback(old_state, new_state) # Shouldn't change state
        assert self.current_state == new_state
        new_state.enter(old_state) # Can change state.

    def reset(self):
        """ Reset all State instances owned by the StateMachine. """
        for state in self.states.values():
            state.reset()

class State:
    """ A class to represent a state in the StateMachine. """
    def __init__(self, parent, name):
        self.parent = parent
        self.name = name

    def enter(self, from_state):
        """ Virtual called when the state is entered.

            It is legal to transition to another state  here. """
        pass

    def leave(self, to_state):
        """ Virtual called when the state is exited. """
        # Handle canceling here.
        pass

    def reset(self):
        """ Pure virtual to reset the state. """
        print self.name
        raise NotImplementedError()

class StatefulRequest(QueueableRequest):
    """ A QueueableRequest which can be processed by a RequestQueueState. """
    def __init__(self, queue):
        QueueableRequest.__init__(self, queue)
        self.tag = None

# Is a delegate which can handle RequestQueue protocol but doesn't
# implement it.
class RequestQueueState(State):
    """ A State subclass which implements the RequestQueue method
        call protocol without subclassing it. """
    def __init__(self, parent, name):
        State.__init__(self, parent, name)
        # ? -> StatefulRequest, key type is implementation dependant
        self.pending = {}

    def reset(self):
        """ Implementation of State virtual. """
        if len(self.pending) > 0:
            print ("BUG?: Reseting state: %s with %i pending requests!" %
                   (self.name, len(self.pending)))

    def next_runnable(self):
        """ Return a MinimalClient instance for the next request to
            be run or None if none is available. """
        pass
        #return None # Trips pylint r201

    def request_progress(self, client, msg):
        """ Handle non-terminal FCP messages for running requests. """
        pass

    def request_done(self, client, msg):
        """ Handle terminal FCP messages for running requests. """
        pass

class DecisionState(RequestQueueState):
    """ Synthetic State which drives a transition to another state
        in enter()."""
    def __init__(self, parent, name):
        RequestQueueState.__init__(self, parent, name)

    def enter(self, from_state):
        """ Immediately drive transition to decide_next_state(). """
        target_state =  self.decide_next_state(from_state)
        assert target_state != self
        assert target_state != from_state
        self.parent.transition(target_state)

    def decide_next_state(self, dummy_from_state):
        """ Pure virtual.

            Return the state to transition into. """
        print "ENOTIMPL:" + self.name
        return ""

    # Doesn't handle FCP requests.
    def next_runnable(self):
        """ Illegal. """
        assert False

    def request_progress(self, dummy_client, dummy_msg):
        """ Illegal. """
        assert False

    def request_done(self, dummy_client, dummy_msg):
        """ Illegal. """
        assert False

class RunningSingleRequest(RequestQueueState):
    """ RequestQueueState to run a single StatefulRequest.

        Caller MUST set request field.
    """
    def __init__(self, parent, name, success_state, failure_state):
        RequestQueueState.__init__(self, parent, name)
        self.success_state = success_state
        self.failure_state = failure_state
        self.request = None
        self.queued = False
        self.final_msg = None

    def enter(self, dummy_from_state):
        """ Implementation of State virtual. """
        assert not self.queued
        assert len(self.pending) == 0
        assert not self.request is None
        assert not self.request.tag is None

    def reset(self):
        """ Implementation of State virtual. """
        RequestQueueState.reset(self)
        self.request = None
        self.queued = False
        self.final_msg = None

    def next_runnable(self):
        """ Send request for the file once."""
        if self.queued:
            return None

        # REDFLAG: sucky code, weird coupling
        self.parent.ctx.set_cancel_time(self.request)

        self.queued = True
        self.pending[self.request.tag] = self.request
        return self.request

    def request_done(self, client, msg):
        """ Implement virtual. """
        assert self.request == client
        del self.pending[self.request.tag]
        self.final_msg = msg
        if msg[0] in SUCCESS_MSGS:
            self.parent.transition(self.success_state)
            return

        self.parent.transition(self.failure_state)

class Quiescent(RequestQueueState):
    """ The quiescent state for the state machine. """
    def __init__(self, parent, name):
        RequestQueueState.__init__(self, parent, name)
        self.prev_state = 'UNKNOWN'

    def enter(self, from_state):
        """ Implementation of State virtual. """
        self.prev_state = from_state.name

    def reset(self):
        """ Implementation of State virtual. """
        self.prev_state = 'UNKNOWN'
        RequestQueueState.reset(self)

    def arrived_from(self, allowed_states):
        """ Returns True IFF the state machine transitioned to this state
            from one of the states in allowed_states, False otherwise. """
        return self.prev_state in allowed_states

class Canceling(RequestQueueState):
    """ State which cancels FCP requests from the previous state and
        waits for them to finish. """

    def __init__(self, parent, name, finished_state):
        RequestQueueState.__init__(self, parent, name)
        self.finished_state = finished_state

    def enter(self, from_state):
        """ Implementation of State virtual. """
        if not hasattr(from_state, 'pending') or len(from_state.pending) == 0:
            self.parent.transition(self.finished_state)
            return

        self.pending = from_state.pending.copy()
        for request in self.pending.values():
            self.parent.runner.cancel_request(request)

    def request_done(self, client, dummy):
        """ Implementation of RequestQueueState virtual. """
        tag = client.tag
        del self.pending[tag]

        if len(self.pending) == 0:
            self.parent.transition(self.finished_state)
            return

class CandidateRequest(StatefulRequest):
    """ A StatefulRequest subclass that was made from
        some kind of candidate. """
    def __init__(self, queue):
        StatefulRequest.__init__(self, queue)
        self.candidate = None

# This is not as well thought out as the other stuff in this file.
# REDFLAG: better name?
class RetryingRequestList(RequestQueueState):
    """ A RequestQueueState subclass which maintains a collection
        of 'candidate' objects which it uses to make request from.

        NOTE:
        The definition of what a candidate is is left to the subclass.
    """
    def __init__(self, parent, name):
        RequestQueueState.__init__(self, parent, name)
        self.current_candidates = []
        self.next_candidates = []
        self.finished_candidates = []

    def reset(self):
        """ Implementation of State virtual. """
        self.current_candidates = []
        self.next_candidates = []
        self.finished_candidates = []
        RequestQueueState.reset(self)

    def next_runnable(self):
        """ Implementation of RequestQueueState virtual. """
        candidate = self.get_candidate()
        if candidate is None:
            return None

        request = self.make_request(candidate)
        self.pending[request.tag] = request
        return request

    def request_done(self, client, msg):
        """ Implementation of RequestQueueState virtual. """
        candidate = client.candidate
        assert not candidate is None
        del self.pending[client.tag]
        # REDFLAG: fix signature? to get rid of candidate
        self.candidate_done(client, msg, candidate)

    ############################################################
    def is_stalled(self):
        """ Returns True if there are no more candidates to run,
            False otherwise. """
        return (len(self.pending) + len(self.current_candidates)
                + len(self.next_candidates) == 0)

    def pending_candidates(self):
        """ Returns the candiates that are currently being run
            by the RequestQueue. """
        return [request.candidate for request in self.pending.values()]

    # ORDER:
    # 0) Candidates are popped of the lists.
    # 1) All candidates are popped off of current before any are popped
    #    off of next.
    # 2) When current is empty AND all pending requests have finished
    #    next and current are swapped.
    def get_candidate(self):
        """ INTERNAL: Gets the next candidate to run, or None if none
            is available. """
        if len(self.current_candidates) == 0:
            if len(self.pending) != 0 or len(self.next_candidates) == 0:
                # i.e. Don't run requests from the next_candidates
                # until requests for current candidates have finished.
                # REDFLAG: Add a parameter to control this behavior?
                return None

            self.current_candidates = self.next_candidates
            self.next_candidates = []
            return self.get_candidate()

        #print "get_candidate -- ", len(self.pending)
        #   len(self.current_candidates), \
        # len(self.next_candidates)
        #print "CURRENT:"
        #print self.current_candidates
        #print "NEXT:"
        #print self.next_candidates

        candidate = self.current_candidates.pop()

        return candidate

    ############################################################
    def candidate_done(self, client, msg, candidate):
        """ Pure virtual.

            Add candidate to next_candidates here to retry.
            Add candidate to finished_candidates here if done. """
        # Commented out to avoid pylint R0922
        #raise NotImplementedError()
        pass

    def make_request(self, dummy_candidate):
        """ Subclasses must return CandidateRequest or CandidateRequest
            subclass for the candidate."""
        #raise NotImplementedError()

        return CandidateRequest(self.parent)