#  SPDX-License-Identifier: BSD-3-Clause
#  Copyright (C) 2017 Intel Corporation.
#  All rights reserved.

import json
import os
import sys

from io import IOBase as io

from . import accel
from . import app
from . import bdev
from . import blobfs
from . import env_dpdk
from . import dsa
from . import iaa
from . import ioat
from . import iscsi
from . import log
from . import lvol
from . import nbd
from . import notify
from . import nvme
from . import nvmf
from . import pmem
from . import subsystem
from . import trace
from . import vhost
from . import vmd
from . import sock
from . import vfio_user
from . import iobuf
from . import client as rpc_client


def framework_start_init(client):
    """Start initialization of subsystems"""
    return client.call('framework_start_init')


def framework_wait_init(client):
    """Block until subsystems have been initialized"""
    return client.call('framework_wait_init')


def framework_disable_cpumask_locks(client):
    """ Disable CPU core lock files."""
    return client.call('framework_disable_cpumask_locks')


def framework_enable_cpumask_locks(client):
    """ Enable CPU core lock files."""
    return client.call('framework_enable_cpumask_locks')


def rpc_get_methods(client, current=None, include_aliases=None):
    """Get list of supported RPC methods.
    Args:
        current: Get list of RPC methods only callable in the current state.
        include_aliases: Include aliases in the list with RPC methods.
    """
    params = {}

    if current:
        params['current'] = current
    if include_aliases:
        params['include_aliases'] = include_aliases

    return client.call('rpc_get_methods', params)


def spdk_get_version(client):
    """Get SPDK version"""
    return client.call('spdk_get_version')


def _json_dump(config, fd, indent):
    if indent is None:
        indent = 2
    elif indent < 0:
        indent = None
    json.dump(config, fd, indent=indent)
    fd.write('\n')


def _json_load(j):
    if j == sys.stdin or isinstance(j, io):
        json_conf = json.load(j)
    elif os.path.exists(j):
        with open(j, "r") as j:
            json_conf = json.load(j)
    else:
        json_conf = json.loads(j)
    return json_conf


def save_config(client, fd, indent=2):
    """Write current (live) configuration of SPDK subsystems and targets to stdout.
    Args:
        fd: opened file descriptor where data will be saved
        indent: Indent level. Value less than 0 mean compact mode.
            Default indent level is 2.
    """
    config = {
        'subsystems': []
    }

    for elem in client.call('framework_get_subsystems'):
        cfg = {
            'subsystem': elem['subsystem'],
            'config': client.call('framework_get_config', {"name": elem['subsystem']})
        }
        config['subsystems'].append(cfg)

    _json_dump(config, fd, indent)


def load_config(client, fd, include_aliases=False):
    """Configure SPDK subsystems and targets using JSON RPC read from stdin.
    Args:
        fd: opened file descriptor where data will be taken from
    """
    json_config = _json_load(fd)

    # remove subsystems with no config
    subsystems = json_config['subsystems']
    for subsystem in list(subsystems):
        if not subsystem['config']:
            subsystems.remove(subsystem)

    # check if methods in the config file are known
    allowed_methods = client.call('rpc_get_methods', {'include_aliases': include_aliases})
    if not subsystems and 'framework_start_init' in allowed_methods:
        framework_start_init(client)
        return

    for subsystem in list(subsystems):
        config = subsystem['config']
        for elem in list(config):
            if 'method' not in elem or elem['method'] not in allowed_methods:
                raise rpc_client.JSONRPCException("Unknown method was included in the config file")

    while subsystems:
        allowed_methods = client.call('rpc_get_methods', {'current': True,
                                                          'include_aliases': include_aliases})
        allowed_found = False

        for subsystem in list(subsystems):
            config = subsystem['config']
            for elem in list(config):
                if 'method' not in elem or elem['method'] not in allowed_methods:
                    continue

                client.call(**elem)
                config.remove(elem)
                allowed_found = True

            if not config:
                subsystems.remove(subsystem)

        if 'framework_start_init' in allowed_methods:
            framework_start_init(client)
            allowed_found = True

        if not allowed_found:
            break

    if subsystems:
        print("Some configs were skipped because the RPC state that can call them passed over.")


def save_subsystem_config(client, fd, indent=2, name=None):
    """Write current (live) configuration of SPDK subsystem to stdout.
    Args:
        fd: opened file descriptor where data will be saved
        indent: Indent level. Value less than 0 mean compact mode.
            Default is indent level 2.
    """
    cfg = {
        'subsystem': name,
        'config': client.call('framework_get_config', {"name": name})
    }

    _json_dump(cfg, fd, indent)


def load_subsystem_config(client, fd):
    """Configure SPDK subsystem using JSON RPC read from stdin.
    Args:
        fd: opened file descriptor where data will be taken from
    """
    subsystem = _json_load(fd)

    if not subsystem['config']:
        return

    allowed_methods = client.call('rpc_get_methods')
    config = subsystem['config']
    for elem in list(config):
        if 'method' not in elem or elem['method'] not in allowed_methods:
            raise rpc_client.JSONRPCException("Unknown method was included in the config file")

    allowed_methods = client.call('rpc_get_methods', {'current': True})
    for elem in list(config):
        if 'method' not in elem or elem['method'] not in allowed_methods:
            continue

        client.call(**elem)
        config.remove(elem)

    if config:
        print("Some configs were skipped because they cannot be called in the current RPC state.")