# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-2.1-only
#
# Copyright (C) 2015 Philippe Proulx <pproulx@efficios.com>
# Copyright (C) 2014 David Goulet <dgoulet@efficios.com>
# Copyright (C) 2015 Jérémie Galarneau <jeremie.galarneau@efficios.com>

from __future__ import unicode_literals
import lttngust.debug as dbg
import struct


# server command header
_server_cmd_header_struct = struct.Struct('>QII')


# server command header size
_SERVER_CMD_HEADER_SIZE = _server_cmd_header_struct.size


# agent protocol symbol size
_LTTNG_SYMBOL_NAME_LEN = 256


class _ServerCmdHeader(object):
    def __init__(self, data_size, cmd_id, cmd_version):
        self.data_size = data_size
        self.cmd_id = cmd_id
        self.cmd_version = cmd_version


def _server_cmd_header_from_data(data):
    try:
        data_size, cmd_id, cmd_version = _server_cmd_header_struct.unpack(data)
    except (Exception) as e:
        dbg._pdebug('cannot decode command header: {}'.format(e))
        return None

    return _ServerCmdHeader(data_size, cmd_id, cmd_version)


class _ServerCmd(object):
    def __init__(self, header):
        self.header = header

    @classmethod
    def from_data(cls, header, data):
        raise NotImplementedError()


class _ServerCmdList(_ServerCmd):
    @classmethod
    def from_data(cls, header, data):
        return cls(header)


class _ServerCmdEnable(_ServerCmd):
    _NAME_OFFSET = 8
    _loglevel_struct = struct.Struct('>II')
    # filter expression size
    _filter_exp_len_struct = struct.Struct('>I')

    def __init__(self, header, loglevel, loglevel_type, name, filter_exp):
        super(self.__class__, self).__init__(header)
        self.loglevel = loglevel
        self.loglevel_type = loglevel_type
        self.name = name
        self.filter_expression = filter_exp
        dbg._pdebug('server enable command {}'.format(self.__dict__))

    @classmethod
    def from_data(cls, header, data):
        try:
            loglevel, loglevel_type = cls._loglevel_struct.unpack_from(data)
            name_start = cls._loglevel_struct.size
            name_end = name_start + _LTTNG_SYMBOL_NAME_LEN
            data_name = data[name_start:name_end]
            name = data_name.rstrip(b'\0').decode()

            filter_exp_start = name_end + cls._filter_exp_len_struct.size
            filter_exp_len, = cls._filter_exp_len_struct.unpack_from(
                data[name_end:filter_exp_start])
            filter_exp_end = filter_exp_start + filter_exp_len

            filter_exp = data[filter_exp_start:filter_exp_end].rstrip(
                b'\0').decode()

            return cls(header, loglevel, loglevel_type, name, filter_exp)
        except (Exception) as e:
            dbg._pdebug('cannot decode enable command: {}'.format(e))
            return None


class _ServerCmdDisable(_ServerCmd):
    def __init__(self, header, name):
        super(self.__class__, self).__init__(header)
        self.name = name

    @classmethod
    def from_data(cls, header, data):
        try:
            name = data.rstrip(b'\0').decode()

            return cls(header, name)
        except (Exception) as e:
            dbg._pdebug('cannot decode disable command: {}'.format(e))
            return None


class _ServerCmdRegistrationDone(_ServerCmd):
    @classmethod
    def from_data(cls, header, data):
        return cls(header)


_SERVER_CMD_ID_TO_SERVER_CMD = {
    1: _ServerCmdList,
    2: _ServerCmdEnable,
    3: _ServerCmdDisable,
    4: _ServerCmdRegistrationDone,
}


def _server_cmd_from_data(header, data):
    if header.cmd_id not in _SERVER_CMD_ID_TO_SERVER_CMD:
        return None

    return _SERVER_CMD_ID_TO_SERVER_CMD[header.cmd_id].from_data(header, data)


_CLIENT_CMD_REPLY_STATUS_SUCCESS = 1
_CLIENT_CMD_REPLY_STATUS_INVALID_CMD = 2


class _ClientCmdReplyHeader(object):
    _payload_struct = struct.Struct('>I')

    def __init__(self, status_code=_CLIENT_CMD_REPLY_STATUS_SUCCESS):
        self.status_code = status_code

    def get_data(self):
        return self._payload_struct.pack(self.status_code)


class _ClientCmdReplyEnable(_ClientCmdReplyHeader):
    pass


class _ClientCmdReplyDisable(_ClientCmdReplyHeader):
    pass


class _ClientCmdReplyList(_ClientCmdReplyHeader):
    _nb_events_struct = struct.Struct('>I')
    _data_size_struct = struct.Struct('>I')

    def __init__(self, names, status_code=_CLIENT_CMD_REPLY_STATUS_SUCCESS):
        super(self.__class__, self).__init__(status_code)
        self.names = names

    def get_data(self):
        upper_data = super(self.__class__, self).get_data()
        nb_events_data = self._nb_events_struct.pack(len(self.names))
        names_data = bytes()

        for name in self.names:
            names_data += name.encode() + b'\0'

        data_size_data = self._data_size_struct.pack(len(names_data))

        return upper_data + data_size_data + nb_events_data + names_data


class _ClientRegisterCmd(object):
    _payload_struct = struct.Struct('>IIII')

    def __init__(self, domain, pid, major, minor):
        self.domain = domain
        self.pid = pid
        self.major = major
        self.minor = minor

    def get_data(self):
        return self._payload_struct.pack(self.domain, self.pid, self.major,
                                         self.minor)
