import itertools as it, operator as op, functools as ft
from os.path import basename, join
from time import time
import os, sys

import logging
log = logging.getLogger(__name__)

statsd_enabled = True
statsd_ip = '0.0.0.0'
collectd_enabled = True
collectd_ip = '0.0.0.0'
metricsd_enabled = False
metricsd_ip = '0.0.0.0'

collectd_types = '/usr/share/collectd/types.db', '/etc/collectd/types.db'
collectd_use_entry_points = False
statsd_flush_time = 10

graphite_max_reconnects = 0 # infinite


### Converters

class Converters:

	@staticmethod
	def _strip_prefix(prefix, val):
		return val[len(prefix):] if val.startswith(prefix) else val

	@staticmethod
	def _dm_resolve(dev, _cache = dict(), _cache_time=600):
		ts_now = time()
		if isinstance(dev, int): dev = 'dm-{}'.format(dev)
		while True:
			if not _cache: ts = 0
			else:
				dev_cached, ts = (None, _cache[None])\
					if dev not in _cache else _cache[dev]
			# Update cache, if necessary
			if ts_now > ts + _cache_time:
				_cache.clear()
				for link in os.listdir('/dev/mapper'):
					try: link_dm = basename(os.readlink(join('/dev/mapper', link)))
					except OSError: continue # EPERM, EINVAL
					_cache[link_dm] = link, ts_now
				_cache[None] = ts_now
				continue # ...and try again
			return dev_cached.replace('.', '_')


	@classmethod
	def _generic(cls, sample):
		splugin = sample['plugin'].strip()
		parts = [splugin]
		if sample.get('plugin_instance'): parts.append(sample['plugin_instance'].strip())
		stype = cls._strip_prefix('{}_'.format(splugin), sample.get('type', '').strip())
		if stype and stype != 'value': parts.append(stype)
		stypei = sample.get('type_instance', '').strip()
		if stypei: parts.append(stypei)
		vname = sample.get('value_name').strip()
		if vname and vname != 'value': parts.append(vname)
		# log.debug('Catchall sample: {!r}, name: {}'.format(sample, '.'.join(parts)))
		return parts


	@classmethod
	def load(cls, sample):
		return 'load', sample['value_name']

	@classmethod
	def cpu(cls, sample):
		return 'cpu', sample['plugin_instance'], sample['type_instance']

	@classmethod
	def nginx(cls, sample):
		st = cls._strip_prefix('nginx_', sample['type'])
		name = ['nginx', st]
		if st == 'requests': name.append('total')
		if sample.get('type_instance'):
			name.append(sample['type_instance'])
		return name

	@classmethod
	def tail(cls, sample):
		if 'plugin_instance' not in sample:
			log.warn('Tail-info without instance: {!r}'.format(sample))
			return
		return [sample['plugin_instance'].split('---', 1)[0]] + sample['type_instance'].split('-')

	@classmethod
	def filecount(cls, sample):
		return sample['plugin_instance'].split('-')

	@classmethod
	def df( cls, sample,
			_st_conv = dict(df_complex='bytes', df_inodes='inodes') ):
		try: st = '{}_{}'.format(_st_conv[sample['type']], sample['type_instance'])
		except KeyError:
			log.info('Unrecognized df sample: {!r}'.format(sample))
			return
		return ['fs', 'allocation'] + sample['plugin_instance'].split('-') + [st]

	@classmethod
	def disk(cls, sample):
		# {'value_name': 'write', 'plugin_instance': 'dm-15', 'type': 'disk_ops'}
		dev = sample['plugin_instance']
		if dev.startswith('dm-'): # try to resolve into lv name
			dev = cls._dm_resolve(dev)
			if dev is None: return # no point to log it w/o proper name
		return 'disk', 'load', dev,\
			'{}_{}'.format(cls._strip_prefix('disk_', sample['type']), sample['value_name'])

	@classmethod
	def dns(cls, sample):
		st = cls._strip_prefix('dns_', sample['type'])
		if st == 'octets': st = '{}_{}'.format(st, sample['value_name'])
		name = ['dns', st]
		if sample.get('type_instance'): name.append(sample['type_instance'])
		return name

	# -- netlink plugin provides all these as well
	# @classmethod
	# def interface(cls, sample):
	# 	return 'network', 'interfaces',\
	# 		sample['plugin_instance'],\
	# 		sample['type'], sample['value_name']

	@classmethod
	def tcpconns(cls, sample):
		return ['network', 'connections']\
			+ sample['plugin_instance'].split('-')\
			+ [sample['type_instance']]

	@classmethod
	def ping(cls, sample):
		return 'network', 'ping',\
			sample['type_instance'].replace('.', '_'),\
			cls._strip_prefix('ping_', sample['type'])

	@classmethod
	def netlink(cls, sample):
		return 'network', 'interfaces',\
			sample['plugin_instance'],\
			sample['type'], sample['value_name']

	@classmethod
	def protocols(cls, sample):
		proto = sample['plugin_instance']
		if proto.endswith('Ext'): proto = proto[:-3]
		proto = proto.lower()
		metric = sample['type_instance']
		if (proto == 'ip' and metric in ['Forwarding', 'DefaultTTL'])\
			or ( proto == 'tcp' and
				(metric.startswith('Rto') or metric in ['MaxConn', 'ArpFilter']) ): return
		return 'network', 'protocol', proto, metric

	@classmethod
	def swap(cls, sample):
		name = ['memory', 'swap']
		if sample['type_instance'] in ['free', 'used']:
			name.append('allocation')
			dev = sample['plugin_instance']
			if dev.startswith('dev_dm'):
				dev = cls._dm_resolve(int(dev[6:][-1]))
				if dev is None: return # no point to log it w/o proper name
			name.append(dev)
			name.append(sample['type_instance'])
			return name
		else:
			name.append(sample['type_instance'])
			return name

	@classmethod
	def memory(cls, sample):
		mtype = sample.get('type_instance')
		if not mtype:
			log.info('Memory sample w/o type: {!r}'.format(sample))
			return
		return 'memory', mtype

	@classmethod
	def vmem(cls, sample):
		name = ['memory']
		ti = sample.get('type_instance')
		if sample['type'] == 'vmpage_io':
			if ti == 'memory': name.append('pages_{}'.format(sample['value_name']))
			elif ti == 'swap': return # already provided by "swap" plugin
		elif sample['value_name'] != 'value': name.append(sample['value_name'])
		elif ti:
			if ti.startswith('slab_'):
				name.append('slabs')
				ti = ti[5:]
			name.append(ti)
		else:
			log.warn('Unidentified vmem sample: {!r}'.format(sample))
			return
		return name

	@classmethod
	def uptime(cls, sample): return ['uptime']

	@classmethod
	def users(cls, sample): return ['users']

	@classmethod
	def processes(cls, sample):
		proc = sample.get('plugin_instance')
		if not proc: # general info
			if sample['type'] == 'fork_rate': return # looks wrong, duplicated in table:/proc/stats
			elif sample['type'] == 'ps_state':
				return 'processes', 'state', sample['type_instance']
		else:
			metric = sample['value_name']
			if sample['type'].startswith('ps_disk_'):
				metric = ['disk', '{}_{}'.format(metric, sample['type'][8:])]
			elif metric == 'value': metric = ['memory', sample['type'][3:]]
			else:
				mtype = sample['type'][3:]
				if mtype == 'pagefaults': metric = ['memory', metric]
				else: metric = ['{}_{}'.format(mtype, metric)] # 2 vals per type here
			return ['processes', 'by_name', proc] + metric

	@classmethod
	def table(cls, sample):
		table = sample['plugin_instance']
		if table == 'stat':
			line = sample['type_instance']
			if line == 'intr': return 'irq', 'total', 'hard'
			elif line == 'softirq': return 'irq', 'total', 'soft'
			elif line == 'processes': return 'processes', 'forks'
			else: return # no need for others
		elif table == 'slabinfo':
			return ['memory', 'slabs'] + sample['type_instance'].split('-', 1)
		else:
			log.info('Sample for unhandled table: {!r}'.format(sample))
			return

	@classmethod
	def py_logtail(cls, sample):
		name = [sample['plugin_instance']] + sample['type_instance'].split('-')
		if '_null' in name: return
		return name

	@classmethod
	def py_proctables(cls, sample):
		if sample['plugin_instance'] == 'slabinfo':
			val_name = sample['value_name']
			if sample['type'] == 'slab_bytes':
				val_name = 'bytes_{}'.format(val_name)
			return 'memory', 'slabs', sample['type_instance'], val_name
		elif sample['plugin_instance'] == 'memstats':
			name, st = ['memory'], sample['type']
			if st == 'vmpage_number': return name + ['pages', 'allocation', sample['type_instance']]
			elif st == 'vmpage_action': return name + ['pages', 'activity', sample['type_instance']]
			elif st == 'memory': return name + ['allocation', sample['type_instance']]
			else: log.warn('Unhandled memstats sample type: {!r}'.format(sample))
		elif sample['plugin_instance'] == 'stats':
			st = sample['type']
			if st == 'irq': return 'irq', 'total', sample['type_instance']
			elif st == 'fork_rate': return 'processes', 'forks'
		elif sample['plugin_instance'] == 'memfrag':
			return ['memory', 'fragmentation'] + sample['type_instance'].split('-')
		elif sample['plugin_instance'] == 'irq':
			return ['irq'] + sample['type_instance'].split('-')
		else:
			log.warn('Unidentified py_proctables sample: {!r}'.format(sample))

	@classmethod
	def conntrack(cls, sample): return 'misc', 'conntrack'
	@classmethod
	def contextswitch(cls, sample): return 'misc', 'contextswitch'
	@classmethod
	def entropy(cls, sample): return 'misc', 'entropy'
	@classmethod
	def uptime(cls, sample): return 'misc', 'uptime'
	@classmethod
	def users(cls, sample): return 'misc', 'users'

	apache = memcached = _generic


def dump(sample, extra=''):
	log.info('Sample{}: {!r}'.format(extra, sample))



### Bindings

collectd_converters = dict(
	(name, getattr(Converters, name))
	for name in dir(Converters) if not name.startswith('_') )

collectd_converters.update(dict(_default=dump))
	# '_default': Converters._generic })
