# Copyright 2013 - Mirantis, Inc.
# Copyright 2015 - Huawei Technologies Co. Ltd
# Copyright 2016 - Brocade Communications Systems, Inc.
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.

import contextlib
import os
import shutil
import tempfile

from oslo_concurrency import processutils
from oslo_serialization import jsonutils


class NotDefined(object):
    """Marker of an empty value.

    In a number of cases None can't be used to express the semantics of
    a not defined value because None is just a normal value rather than
    a value set to denote that it's not defined. This class can be used
    in such cases instead of None.
    """

    pass


def get_dict_from_string(string, delimiter=','):
    if not string:
        return {}

    kv_dicts = []

    for kv_pair_str in string.split(delimiter):
        kv_str = kv_pair_str.strip()
        kv_list = kv_str.split('=')

        if len(kv_list) > 1:
            try:
                value = jsonutils.loads(kv_list[1])
            except ValueError:
                value = kv_list[1]

            kv_dicts += [{kv_list[0]: value}]
        else:
            kv_dicts += [kv_list[0]]

    return get_dict_from_entries(kv_dicts)


def get_dict_from_entries(entries):
    """Transforms a list of entries into dictionary.

    :param entries: A list of entries.
        If an entry is a dictionary the method simply updates the result
        dictionary with its content.
        If an entry is not a dict adds {entry, NotDefined} into the result.
    """

    result = {}

    for e in entries:
        if isinstance(e, dict):
            result.update(e)
        else:
            # NOTE(kong): we put NotDefined here as the value of
            # param without value specified, to distinguish from
            # the valid values such as None, ''(empty string), etc.
            result[e] = NotDefined

    return result


@contextlib.contextmanager
def tempdir(**kwargs):
    argdict = kwargs.copy()

    if 'dir' not in argdict:
        argdict['dir'] = '/tmp/'

    tmpdir = tempfile.mkdtemp(**argdict)

    try:
        yield tmpdir
    finally:
        try:
            shutil.rmtree(tmpdir)
        except OSError as e:
            raise OSError(
                "Failed to delete temp dir %(dir)s (reason: %(reason)s)" %
                {'dir': tmpdir, 'reason': e}
            )


def save_text_to(text, file_path, overwrite=False):
    if os.path.exists(file_path) and not overwrite:
        raise OSError(
            "Cannot save data to file. File %s already exists."
        )

    with open(file_path, 'w') as f:
        f.write(text)


def generate_key_pair(key_length=2048):
    """Create RSA key pair with specified number of bits in key.

    Returns tuple of private and public keys.
    """
    with tempdir() as tmpdir:
        keyfile = os.path.join(tmpdir, 'tempkey')
        args = [
            'ssh-keygen',
            '-q',  # quiet
            '-N', '',  # w/o passphrase
            '-t', 'rsa',  # create key of rsa type
            '-f', keyfile,  # filename of the key file
            '-C', 'Generated-by-Mistral'  # key comment
        ]

        if key_length is not None:
            args.extend(['-b', key_length])

        processutils.execute(*args)

        if not os.path.exists(keyfile):
            # raise exc.DataAccessException(
            #     "Private key file hasn't been created"
            # )
            raise OSError("Private key file hasn't been created")

        private_key = open(keyfile).read()
        public_key_path = keyfile + '.pub'

        if not os.path.exists(public_key_path):
            # raise exc.DataAccessException(
            #     "Public key file hasn't been created"
            # )
            raise OSError("Private key file hasn't been created")
        public_key = open(public_key_path).read()

        return private_key, public_key
