# -*- coding: utf-8 -*-
# Description: memcached netdata python.d module
# Author: Pawel Krupa (paulfantom)
# SPDX-License-Identifier: GPL-3.0-or-later

from bases.FrameworkServices.SocketService import SocketService

ORDER = [
    'cache',
    'net',
    'connections',
    'items',
    'evicted_reclaimed',
    'get',
    'get_rate',
    'set_rate',
    'cas',
    'delete',
    'increment',
    'decrement',
    'touch',
    'touch_rate',
]

CHARTS = {
    'cache': {
        'options': [None, 'Cache Size', 'MiB', 'cache', 'memcached.cache', 'stacked'],
        'lines': [
            ['avail', 'available', 'absolute', 1, 1 << 20],
            ['used', 'used', 'absolute', 1, 1 << 20]
        ]
    },
    'net': {
        'options': [None, 'Network', 'kilobits/s', 'network', 'memcached.net', 'area'],
        'lines': [
            ['bytes_read', 'in', 'incremental', 8, 1000],
            ['bytes_written', 'out', 'incremental', -8, 1000],
        ]
    },
    'connections': {
        'options': [None, 'Connections', 'connections/s', 'connections', 'memcached.connections', 'line'],
        'lines': [
            ['curr_connections', 'current', 'incremental'],
            ['rejected_connections', 'rejected', 'incremental'],
            ['total_connections', 'total', 'incremental']
        ]
    },
    'items': {
        'options': [None, 'Items', 'items', 'items', 'memcached.items', 'line'],
        'lines': [
            ['curr_items', 'current', 'absolute'],
            ['total_items', 'total', 'absolute']
        ]
    },
    'evicted_reclaimed': {
        'options': [None, 'Items', 'items', 'items', 'memcached.evicted_reclaimed', 'line'],
        'lines': [
            ['reclaimed', 'reclaimed', 'absolute'],
            ['evictions', 'evicted', 'absolute']
        ]
    },
    'get': {
        'options': [None, 'Requests', 'requests', 'get ops', 'memcached.get', 'stacked'],
        'lines': [
            ['get_hits', 'hits', 'percent-of-absolute-row'],
            ['get_misses', 'misses', 'percent-of-absolute-row']
        ]
    },
    'get_rate': {
        'options': [None, 'Rate', 'requests/s', 'get ops', 'memcached.get_rate', 'line'],
        'lines': [
            ['cmd_get', 'rate', 'incremental']
        ]
    },
    'set_rate': {
        'options': [None, 'Rate', 'requests/s', 'set ops', 'memcached.set_rate', 'line'],
        'lines': [
            ['cmd_set', 'rate', 'incremental']
        ]
    },
    'delete': {
        'options': [None, 'Requests', 'requests', 'delete ops', 'memcached.delete', 'stacked'],
        'lines': [
            ['delete_hits', 'hits', 'percent-of-absolute-row'],
            ['delete_misses', 'misses', 'percent-of-absolute-row'],
        ]
    },
    'cas': {
        'options': [None, 'Requests', 'requests', 'check and set ops', 'memcached.cas', 'stacked'],
        'lines': [
            ['cas_hits', 'hits', 'percent-of-absolute-row'],
            ['cas_misses', 'misses', 'percent-of-absolute-row'],
            ['cas_badval', 'bad value', 'percent-of-absolute-row']
        ]
    },
    'increment': {
        'options': [None, 'Requests', 'requests', 'increment ops', 'memcached.increment', 'stacked'],
        'lines': [
            ['incr_hits', 'hits', 'percent-of-absolute-row'],
            ['incr_misses', 'misses', 'percent-of-absolute-row']
        ]
    },
    'decrement': {
        'options': [None, 'Requests', 'requests', 'decrement ops', 'memcached.decrement', 'stacked'],
        'lines': [
            ['decr_hits', 'hits', 'percent-of-absolute-row'],
            ['decr_misses', 'misses', 'percent-of-absolute-row']
        ]
    },
    'touch': {
        'options': [None, 'Requests', 'requests', 'touch ops', 'memcached.touch', 'stacked'],
        'lines': [
            ['touch_hits', 'hits', 'percent-of-absolute-row'],
            ['touch_misses', 'misses', 'percent-of-absolute-row']
        ]
    },
    'touch_rate': {
        'options': [None, 'Rate', 'requests/s', 'touch ops', 'memcached.touch_rate', 'line'],
        'lines': [
            ['cmd_touch', 'rate', 'incremental']
        ]
    }
}


class Service(SocketService):
    def __init__(self, configuration=None, name=None):
        SocketService.__init__(self, configuration=configuration, name=name)
        self.order = ORDER
        self.definitions = CHARTS
        self.request = 'stats\r\n'
        self.host = 'localhost'
        self.port = 11211
        self._keep_alive = True
        self.unix_socket = None

    def _get_data(self):
        """
        Get data from socket
        :return: dict
        """
        response = self._get_raw_data()
        if response is None:
            # error has already been logged
            return None

        if response.startswith('ERROR'):
            self.error('received ERROR')
            return None

        try:
            parsed = response.split('\n')
        except AttributeError:
            self.error('response is invalid/empty')
            return None

        # split the response
        data = {}
        for line in parsed:
            if line.startswith('STAT'):
                try:
                    t = line[5:].split(' ')
                    data[t[0]] = t[1]
                except (IndexError, ValueError):
                    self.debug('invalid line received: ' + str(line))

        if not data:
            self.error("received data doesn't have any records")
            return None

        # custom calculations
        try:
            data['avail'] = int(data['limit_maxbytes']) - int(data['bytes'])
            data['used'] = int(data['bytes'])
        except (KeyError, ValueError, TypeError):
            pass

        return data

    def _check_raw_data(self, data):
        if data.endswith('END\r\n'):
            self.debug('received full response from memcached')
            return True

        self.debug('waiting more data from memcached')
        return False

    def check(self):
        """
        Parse configuration, check if memcached is available
        :return: boolean
        """
        self._parse_config()
        data = self._get_data()
        if data is None:
            return False
        return True
