#! coding: utf-8

from pymogile.backend import Backend
from pymogile.exceptions import MogileFSError


class Admin(object):
    def __init__(self, trackers, readonly=False, timeout=None):
        self.readonly = bool(readonly)
        self.backend = Backend(trackers, timeout)

    def replicate_now(self):
        return self.backend.do_request("replicate_now")

    def get_hosts(self, hostid=None):
        if hostid:
            params = {'hostid': hostid}
        else:
            params = None
        res = self.backend.do_request("get_hosts", params)
        print res
        results = []
        fields = [
            "hostid", "status", "hostname", "hostip", "http_port",
            "http_get_port", "altip altmask"
        ]
        hosts = int(res['hosts']) + 1
        for ct in xrange(1, hosts):
            results.append(
                dict([(f, res.get('host%d_%s' % (ct, f))) for f in fields])
            )
        return results

    def get_devices(self, devid=None):
        if devid:
            params = {'devid': devid}
        else:
            params = None
        res = self.backend.do_request("get_devices", params)
        ret = []
        for x in xrange(1, int(res['devices']) + 1):
            device = {}
            for k in (
                'devid', 'hostid', 'status', 'observed_state', 'utilization'
            ):
                device[k] = res.get('dev%d_%s' % (x, k))

            for k in ('mb_total', 'mb_used', 'weight'):
                value = res.get('dev%d_%s' % (x, k))
                if value:
                    device[k] = int(value)
                else:
                    device[k] = None
            ret.append(device)
        return ret

    def list_fids(self, from_fid, to_fid):
        """
        get raw information about fids, for enumerating the dataset
        ( from_fid, to_fid )
        returns:
        {fid => {dict with keys: domain, class, devcount, length, key}}
        """
        res = self.backend.do_request(
            'list_fids', {'from': from_fid,
                          'to': to_fid}
        )
        results = {}
        for x in xrange(1, int(res['fid_count']) + 1):
            key = 'fid_%d_fid' % x
            results[key] = dict([(k, res['fid_%d_%s' % (x, k)]) \
                                 for k in ('key', 'length', 'class', 'domain', 'devcount')])
        return results

    def clear_cache(self):
        return self.backend.do_request('clear_cache')

    def get_domains(self):
        """
        get a dict of the domains we know about in the format of
        {
            "domain_name": {
            "class_name": mindevcount,
            "class_name": mindevcount,
            ...
                            },
            ...
        }
        """
        res = self.backend.do_request('get_domains')
        domain_length = int(res['domains'])
        ret = {}
        for x in xrange(1, domain_length + 1):
            domain_name = res['domain%d' % x]
            ret.setdefault(domain_name, {})
            class_length = int(res['domain%dclasses' % x])
            for y in xrange(1, class_length + 1):
                k = 'domain%dclass%dname' % (x, y)
                v = 'domain%dclass%dmindevcount' % (x, y)
                ret[domain_name][res[k]] = int(res[v])
        return ret

    def create_domain(self, domain):
        """
        create a new domain
        """
        if self.readonly:
            return False
        res = self.backend.do_request('create_domain', {'domain': domain})
        if res['domain'] == domain:
            return True
        else:
            return False

    def delete_domain(self, domain):
        """
        delete a domain
        """
        if self.readonly:
            return False
        try:
            res = self.backend.do_request('delete_domain', {'domain': domain})
        except MogileFSError:
            return False
        if res['domain'] == domain:
            return True
        else:
            return False

    def create_class(self, domain, cls, mindevcount):
        """
        create a class within a domain
        """
        return self._modify_class('create', domain, cls, mindevcount)

    def update_class(self, domain, cls, mindevcount):
        """
        update a class's mindevcount within a domain
        """
        return self._modify_class('update', domain, cls, mindevcount)

    def delete_class(self, domain, cls):
        """
        delete a class
        """
        if self.readonly:
            return False
        res = self.backend.do_request(
            "delete_class", {'domain': domain,
                             'class': cls}
        )
        if res['class'] == cls:
            return True
        else:
            return False

    def create_host(self, host, ip, port, getport=None, status=None):
        params = {'host': host, 'ip': ip, 'port': port, 'getport': getport}
        if status:
            params['status'] = status
        return self._modify_host('create', params)

    def update_host(self, host, ip=None, port=None, getport=None, status=None):
        params = {'host': host}
        if ip:
            params['ip'] = ip
        if port:
            params['port'] = port
        if getport:
            params['getport'] = True
        if status:
            params['status'] = status
        return self._modify_host('update', params)

    def delete_host(self, host):
        self.backend.do_request("delete_host", {'host': host})

    def create_device(self, hostname, devid, hostip=None, state=None):
        params = {'hostname': hostname, 'devid': devid}
        if hostip:
            params['hostip'] = hostip
        if state:
            params['state'] = state
        return self.backend.do_request('create_device', params)

    def update_device(self, host, device, status=None, weight=None):
        if status:
            self.change_device_state(host, device, status)

        if weight:
            self.change_device_weight(host, device, weight)
        return True

    def change_device_state(self, host, device, state):
        """
        change the state of a device; pass in the hostname of the host the
        device is located on, the device id number, and the state you want
        the host to be set to.
        """
        params = {'host': host, 'device': device, 'state': state}
        return self.backend.do_request('set_state', params)

    def change_device_weight(self, host, device, weight):
        """
        change the weight of a device by passing in the hostname and
        the device id
        """
        if not isinstance(weight, (int, long)):
            raise ValueError('argument weight muse be an integer')
        params = {'host': host, 'device': device, 'weight': weight}
        return self.backend.do_request('set_weight', params)

    def _get_slave_keys(self):
        res = self.backend.do_request("server_setting", {"key": "slave_keys"})
        if not res:
            return {}

        value = res['value']
        slave_keys = {}
        for slave in value.split(','):
            key, weight = (slave.split("=", 1) + [None])[:2]
            # Weight can be zero,
            # so don't default to 1 if it's defined and longer than 0 characters.
            try:
                weight = int(weight)
                if not weight:
                    weight = 1
            except (TypeError, ValueError):
                weight = 1

            slave_keys[key] = weight
        return slave_keys

    def _set_slave_keys(self, keys):
        buf = []
        for key, weight in keys.items():
            try:
                weight = int(weight)
                if weight != 1:
                    key = "%s=%d" % (key, weight)
            except (TypeError, ValueError):
                pass

            buf.append(key)

        self.backend.do_request(
            "set_server_setting", {'key': 'slave_keys',
                                   'value': ''.join(buf)}
        )

    def slave_list(self):
        keys = self._get_slave_keys()
        ret = {}
        for key in keys:
            res = self.backend.do_request(
                'server_setting', {'key': 'slave_%s' % key}
            )
            if not res:
                continue

            value = res['value']
            dsn, username, password = (value.split('|') + ['', ''])[:3]
            ret[key] = (dsn, username, password)

        return ret

    def slave_add(self, key, dsn, username, password):
        keys = self._get_slave_keys()
        if key in keys:
            return

        value = '|'.join([dsn, username, password])
        self.backend.do_request(
            "set_server_setting", {'key': key,
                                   'value': value}
        )
        keys[key] = None
        self._set_slave_keys(keys)

    def slave_modify(self, key, **opts):
        keys = self._get_slave_keys()
        if key not in keys:
            # slave not fuond
            return
        res = self.backend.do_request(
            "server_setting", {"key": "slave_%s" % key}
        )
        value = res['value']
        dsn, username, password = (value.split('|') + ['', ''])[:3]

        dsn = opts.get('dsn') or dsn
        username = opts.get('username') or username
        password = opts.get('password') or password

        value = '|'.join([dsn, username, password])
        res = self.backend.do_request(
            'set_server_setting', {'key': 'slave_%s' % key,
                                   'value': value}
        )

    def slave_delete(self, key):
        slave_keys = self._get_slave_keys()
        if not slave_keys:
            return None

        if key not in slave_keys:
            return False

        self.backend.do_request('set_server_setting', {key: "slave_%s" % key})
        del slave_keys[key]
        self._set_slave_keys(slave_keys)

    def fsck_start(self):
        return self.backend.do_request("fsck_start")

    def fsck_stop(self):
        return self.backend.do_request("fsck_stop")

    def fsck_reset(self, policy_only, startpos):
        return self.backend.do_request(
            "fsck_reset", {'policy_only': policy_only,
                           'startpos': startpos}
        )

    def fsck_clearlog(self):
        return self.backend.do_request("fsck_clearlog")

    def fsck_status(self):
        return self.backend.do_request("fsck_status")

    def fsck_log_rows(self, after_logid=None):
        params = {}
        if after_logid:
            params['after_logid'] = after_logid
        res = self.backend.do_request("fsck_getlog", params)

        row_count = int(res['row_count'])
        ret = []
        for x in xrange(1, row_count + 1):
            rec = {}
            for k in ("logid", "utime", "fid", "evcode", "devid"):
                rec[k] = res.get("row_%d_%s" % (x, k))
            ret.append(rec)
        return ret

    def set_server_setting(self, key, value):
        params = {'key': key, 'value': value}
        return self.backend.do_request("set_server_setting", params)

    def server_settings(self):
        res = self.backend.do_request("server_settings")
        if not res:
            return
        ret = {}
        for x in xrange(1, int(res["key_count"]) + 1):
            key = res.get("key_%d" % x, '')
            value = res.get("value_%d" % x, '')
            ret[key] = value
        return ret

    def _modify_class(self, verb, domain, cls, mindevcount, replpolicy=None):
        if self.readonly:
            return False
        params = {'domain': domain, 'class': cls, 'mindevcount': mindevcount}
        if replpolicy:
            params['replpolicy'] = replpolicy
        res = self.backend.do_request("%s_class" % verb, params)
        if res['class'] == cls:
            return True
        else:
            return False

    def _modify_host(self, verb, params):
        if self.readonly:
            return False
        try:
            return self.backend.do_request("%s_host" % verb, params)
        except MogileFSError:
            return False

    ## Extra
    #
    def get_freespace(self, devid=None):
        """Get the free space for the entire cluster, or a specific node"""
        return sum([x['mb_free'] for x in self.get_devices(devid)])

    def get_stats(self):
        params = {'all': 1}
        res = self.backend.do_request('stats', params)

        ret = {}
        # get replication statistics
        if 'replicationcount' in res:
            replication = ret.setdefault('replication', {})
            for x in xrange(1, int(res['replicationcount']) + 1):
                domain = res.get('replication%ddomain' % x, '')
                cls = res.get('replication%dclass' % x, '')
                devcount = res.get('replication%ddevcount' % x, '')
                fields = res.get('replication%dfields' % x)
                (replication.setdefault(domain, {}).setdefault(cls, {})
                 )[devcount] = fields

        # get file statistics
        if 'filescount' in res:
            files = ret.setdefault('files', {})
            for x in xrange(1, int(res['filescount']) + 1):
                domain = res.get('files%ddomain' % x, '')
                cls = res.get('files%dclass' % x, '')
                (files.setdefault(domain, {}
                                  ))[cls] = res.get('files%dfiles' % x)

        # get device statistics
        if 'devicescount' in res:
            devices = ret.setdefault('devices', {})
            for x in xrange(1, int(res['devicescount']) + 1):
                key = res.get('devices%did' % x, '')
                devices[key] = {
                    'host': res.get('devices%dhost' % x),
                    'status': res.get('devices%dstatus' % x),
                    'files': res.get('devices%dfiles' % x),
                }

        if 'fidmax' in res:
            ret['fids'] = {'max': res['fidmax']}

        # return the created response
        return ret
