import collections
import confluent.exceptions as exc
import confluent.interface.console as conapi
import confluent.messages as msg
import eventlet
import eventlet.event
import eventlet.green.threading as threading
import eventlet.greenpool as greenpool
import eventlet.queue
import os
import pyghmi.exceptions as pygexc
import pyghmi.ipmi.console as console
import pyghmi.ipmi.command as ipmicommand
console.session.select = eventlet.green.select
console.session.threading = eventlet.green.threading

_ipmithread = None

def _ipmi_evtloop():
    while True:
        try:
            console.session.Session.wait_for_rsp(timeout=600)
        except:
            import traceback
            traceback.print_exc()


def get_conn_params(node, configdata):
    if 'secret.ipmiuser' in configdata:
        username = configdata['secret.ipmiuser']['value']
    elif 'secret.managementuser' in configdata:
        username = configdata['secret.managementuser']['value']
    else:
        username = 'USERID'
    if 'secret.ipmipassphrase' in configdata:
        passphrase = configdata['secret.ipmipassphrase']['value']
    elif 'secret.managementpassphrase' in configdata:
        passphrase = configdata['secret.managementpassphrase']['value']
    else:
        passphrase = 'PASSW0RD' # for lack of a better guess
    if 'hardwaremanagement.manager' in configdata:
        bmc = configdata['hardwaremanagement.manager']['value']
    else:
        bmc = node
    if 'secret.ipmikg' in configdata:
        kg = configdata['secret.ipmikg']['value']
    else:
        kg = passphrase
    #TODO(jbjohnso): check if the end has some number after a : without []
    #for non default port
    return {
        'username': username,
        'passphrase': passphrase,
        'kg': kg,
        'bmc': bmc,
        'port': 623,
    }


class IpmiConsole(conapi.Console):
    def __init__(self, node, config):
        crypt = config.decrypt
        config.decrypt = True
        self.broken = False
        configdata = config.get_node_attributes([node],
            ['secret.ipmiuser', 'secret.ipmipassphrase',
             'secret.managementuser', 'secret.managementpassphrase',
             'hardwaremanagement.manager'])
        connparams = get_conn_params(node, configdata[node])
        self.username = connparams['username']
        self.password = connparams['passphrase']
        self.kg = connparams['kg']
        self.bmc = connparams['bmc']
        self.port = connparams['port']
        # Cannot actually create console until 'connect', when we get callback

    def handle_data(self, data):
        if type(data) == dict:
            disconnect = frozenset(('Session Disconnected', 'timeout'))
            if 'error' in data and data['error'] in disconnect:
                self.broken = True
                self.datacallback(conapi.ConsoleEvent.Disconnect)
            else:
                raise Exception("Unrecognized pyghmi input %s" % repr(data))
        else:
            self.datacallback(data)

    def connect(self,callback):
        self.datacallback = callback
        self.solconnection = console.Console(bmc=self.bmc, port=self.port,
                                           userid=self.username,
                                           password=self.password, kg=self.kg,
                                           force=True,
                                           iohandler=self.handle_data)

    def write(self, data):
        self.solconnection.send_data(data)


class IpmiIterator(object):
    def __init__(self, operator, nodes, element, cfg, inputdata):
        crypt = cfg.decrypt
        cfg.decrypt = True
        configdata = cfg.get_node_attributes(nodes,
            ['secret.ipmiuser', 'secret.ipmipassphrase',
             'secret.managementuser', 'secret.managementpassphrase',
             'hardwaremanagement.manager'])
        cfg.decrypt = crypt
        self.gpile = greenpool.GreenPile()
        for node in nodes:
            self.gpile.spawn(perform_request, operator, node, element, configdata, inputdata)

    def __iter__(self):
        return self

    def next(self):
        ndata = self.gpile.next()
        # need to apply any translations between pyghmi and confluent
        return ndata


def perform_request(operator, node, element, configdata, inputdata):
    return IpmiHandler(operator, node, element, configdata, inputdata).handle_request()


class IpmiHandler(object):
    def __init__(self, operation, node, element, cfd, inputdata):
        self.broken = False
        eventlet.sleep(0)
        self.cfg = cfd[node]
        self.loggedin = False
        self.node = node
        self.element = element
        self.op = operation
        connparams = get_conn_params(node, self.cfg)
        self.ipmicmd = None
        self._logevt = threading.Event()
        self.inputdata = inputdata
        self.ipmicmd = ipmicommand.Command(bmc=connparams['bmc'],
                                           userid=connparams['username'],
                                           password=connparams['passphrase'],
                                           kg=connparams['kg'],
                                           port=connparams['port'],
                                           onlogon=self.logged)

    bootdevices = {
        'optical': 'cd'
    }

    def logged(self, response, ipmicmd):
        if 'error' in response:
            self.broken = True
            self.error = response['error']
        else:
            self.loggedin = True
        self._logevt.set()

    def handle_request(self):
        self._logevt.wait()
        if self.broken:
            if self.error == 'timeout':
                raise exc.TargetEndpointTimeout()
            else:
                raise Exception(self.error)
        if self.element == [ 'power', 'state' ]:
            return self.power()
        elif self.element == [ 'boot', 'device' ]:
            return self.bootdevice()

    def bootdevice(self):
        if 'read' == self.op:
            bootdev = self.ipmicmd.get_bootdev()
            if bootdev['bootdev'] in self.bootdevices:
                bootdev['bootdev'] = self.bootdevices[bootdev['bootdev']]
            return msg.BootDevice(node=self.node,
                    device=bootdev['bootdev'])
        elif 'update' == self.op:
            bootdev = self.inputdata.bootdevice(self.node)
            bootdev = self.ipmicmd.set_bootdev(bootdev)
            if bootdev['bootdev'] in self.bootdevices:
                bootdev['bootdev'] = self.bootdevices[bootdev['bootdev']]
            return msg.BootDevice(node=self.node,
                    device=bootdev['bootdev'])

    def power(self):
        if 'read' == self.op:
            power = self.ipmicmd.get_power()
            return msg.PowerState(node=self.node,
                                  state=power['powerstate'])
        elif 'update' == self.op:
            powerstate = self.inputdata.powerstate(self.node)
            #TODO: call with wait argument
            self.ipmicmd.set_power(powerstate)
            power = self.ipmicmd.get_power()
            return msg.PowerState(node=self.node,
                                  state=power['powerstate'])



def initthread():
    global _ipmithread
    if _ipmithread is None:
        _ipmithread = eventlet.spawn(_ipmi_evtloop)


def create(nodes, element, configmanager, inputdata):
    initthread()
    if element == [ '_console', 'session' ]:
        if len(nodes) > 1:
            raise Exception("_console/session does not support multiple nodes")
        return IpmiConsole(nodes[0], configmanager)
    else:
        return IpmiIterator('update', nodes, element, configmanager, inputdata)

def update(nodes, element, configmanager, inputdata):
    initthread()
    return create(nodes, element, configmanager, inputdata)



def retrieve(nodes, element, configmanager, inputdata):
    initthread()
    return IpmiIterator('read', nodes, element, configmanager, inputdata)