# -*- coding: utf-8 -*-
# Description: BOINC netdata python.d module
# Author: Austin S. Hemmelgarn (Ferroin)
# SPDX-License-Identifier: GPL-3.0-or-later

import socket

from bases.FrameworkServices.SimpleService import SimpleService

from third_party import boinc_client


ORDER = [
    'tasks',
    'states',
    'sched_states',
    'process_states',
]

CHARTS = {
    'tasks': {
        'options': [None, 'Overall Tasks', 'tasks', 'boinc', 'boinc.tasks', 'line'],
        'lines': [
            ['total', 'Total', 'absolute', 1, 1],
            ['active', 'Active', 'absolute', 1, 1]
        ]
    },
    'states': {
        'options': [None, 'Tasks per State', 'tasks', 'boinc', 'boinc.states', 'line'],
        'lines': [
            ['new', 'New', 'absolute', 1, 1],
            ['downloading', 'Downloading', 'absolute', 1, 1],
            ['downloaded', 'Ready to Run', 'absolute', 1, 1],
            ['comperror', 'Compute Errors', 'absolute', 1, 1],
            ['uploading', 'Uploading', 'absolute', 1, 1],
            ['uploaded', 'Uploaded', 'absolute', 1, 1],
            ['aborted', 'Aborted', 'absolute', 1, 1],
            ['upload_failed', 'Failed Uploads', 'absolute', 1, 1]
        ]
    },
    'sched_states': {
        'options': [None, 'Tasks per Scheduler State', 'tasks', 'boinc', 'boinc.sched', 'line'],
        'lines': [
            ['uninit_sched', 'Uninitialized', 'absolute', 1, 1],
            ['preempted', 'Preempted', 'absolute', 1, 1],
            ['scheduled', 'Scheduled', 'absolute', 1, 1]
        ]
    },
    'process_states': {
        'options': [None, 'Tasks per Process State', 'tasks', 'boinc', 'boinc.process', 'line'],
        'lines': [
            ['uninit_proc', 'Uninitialized', 'absolute', 1, 1],
            ['executing', 'Executing', 'absolute', 1, 1],
            ['suspended', 'Suspended', 'absolute', 1, 1],
            ['aborting', 'Aborted', 'absolute', 1, 1],
            ['quit', 'Quit', 'absolute', 1, 1],
            ['copy_pending', 'Copy Pending', 'absolute', 1, 1]
        ]
    }
}

# A simple template used for pre-loading the return dictionary to make
# the _get_data() method simpler.
_DATA_TEMPLATE = {
    'total': 0,
    'active': 0,
    'new': 0,
    'downloading': 0,
    'downloaded': 0,
    'comperror': 0,
    'uploading': 0,
    'uploaded': 0,
    'aborted': 0,
    'upload_failed': 0,
    'uninit_sched': 0,
    'preempted': 0,
    'scheduled': 0,
    'uninit_proc': 0,
    'executing': 0,
    'suspended': 0,
    'aborting': 0,
    'quit': 0,
    'copy_pending': 0
}

# Map task states to dimensions
_TASK_MAP = {
    boinc_client.ResultState.NEW: 'new',
    boinc_client.ResultState.FILES_DOWNLOADING: 'downloading',
    boinc_client.ResultState.FILES_DOWNLOADED: 'downloaded',
    boinc_client.ResultState.COMPUTE_ERROR: 'comperror',
    boinc_client.ResultState.FILES_UPLOADING: 'uploading',
    boinc_client.ResultState.FILES_UPLOADED: 'uploaded',
    boinc_client.ResultState.ABORTED: 'aborted',
    boinc_client.ResultState.UPLOAD_FAILED: 'upload_failed'
}

# Map scheduler states to dimensions
_SCHED_MAP = {
    boinc_client.CpuSched.UNINITIALIZED: 'uninit_sched',
    boinc_client.CpuSched.PREEMPTED: 'preempted',
    boinc_client.CpuSched.SCHEDULED: 'scheduled',
}

# Maps process states to dimensions
_PROC_MAP = {
    boinc_client.Process.UNINITIALIZED: 'uninit_proc',
    boinc_client.Process.EXECUTING: 'executing',
    boinc_client.Process.SUSPENDED: 'suspended',
    boinc_client.Process.ABORT_PENDING: 'aborted',
    boinc_client.Process.QUIT_PENDING: 'quit',
    boinc_client.Process.COPY_PENDING: 'copy_pending'
}


class Service(SimpleService):
    def __init__(self, configuration=None, name=None):
        SimpleService.__init__(self, configuration=configuration, name=name)
        self.order = ORDER
        self.definitions = CHARTS
        self.host = self.configuration.get('host', 'localhost')
        self.port = self.configuration.get('port', 0)
        self.password = self.configuration.get('password', '')
        self.client = boinc_client.BoincClient(host=self.host, port=self.port, passwd=self.password)
        self.alive = False

    def check(self):
        return self.connect()

    def connect(self):
        self.client.connect()
        self.alive = self.client.connected and self.client.authorized
        return self.alive

    def reconnect(self):
        # The client class itself actually disconnects existing
        # connections when it is told to connect, so we don't need to
        # explicitly disconnect when we're just trying to reconnect.
        return self.connect()

    def is_alive(self):
        if not self.alive:
            return self.reconnect()
        return True

    def _get_data(self):
        if not self.is_alive():
            return None

        data = dict(_DATA_TEMPLATE)

        try:
            results = self.client.get_tasks()
        except socket.error:
            self.error('Connection is dead')
            self.alive = False
            return None

        for task in results:
            data['total'] += 1
            data[_TASK_MAP[task.state]] += 1
            try:
                if task.active_task:
                    data['active'] += 1
                    data[_SCHED_MAP[task.scheduler_state]] += 1
                    data[_PROC_MAP[task.active_task_state]] += 1
            except AttributeError:
                pass

        return data or None
