#
# Copyright 2017 the original author or authors.
#
# 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.
#

"""
A config rev object that persists itself
"""
from __future__ import absolute_import
from bz2 import compress, decompress

import structlog
from simplejson import dumps, loads

from pyvoltha.common.config.config_rev import ConfigRevision, children_fields
import six

log = structlog.get_logger()


class PersistedConfigRevision(ConfigRevision):

    compress = False

    __slots__ = ('_kv_store',)

    def __init__(self, branch, data, children=None):
        self._kv_store = branch._node._root._kv_store
        super(PersistedConfigRevision, self).__init__(branch, data, children)

    def _finalize(self):
        super(PersistedConfigRevision, self)._finalize()
        self.store()

    def __del__(self):
        try:
            if self._hash:
                if self._config.__weakref__ is None:
                    if self._config._hash in self._kv_store:
                        del self._kv_store[self._config._hash]
                assert self.__weakref__ is None
                if self._hash in self._kv_store:
                    del self._kv_store[self._hash]
        except Exception as e:
            # this should never happen
            log.exception('del-error', hash=self.hash, e=e)

    def store(self):

        try:
            # crude serialization of children hash and config data hash
            if self._hash in self._kv_store:
                return

            self.store_config()

            children_lists = {}
            for field_name, children in six.iteritems(self._children):
                hashes = [rev.hash for rev in children]
                children_lists[field_name] = hashes

            data = dict(
                children=children_lists,
                config=self._config._hash
            )
            blob = dumps(data)
            if self.compress:
                blob = compress(blob)

            self._kv_store[self._hash] = blob

        except Exception as e:
            log.exception('store-error', e=e)

    @classmethod
    def load(cls, branch, kv_store, msg_cls, hash):
        #  Update the branch's config store
        blob = kv_store[hash]
        if cls.compress:
            blob = decompress(blob)
        data = loads(blob)

        config_hash = data['config']
        config_data = cls.load_config(kv_store, msg_cls, config_hash)

        children_list = data['children']
        assembled_children = {}
        node = branch._node
        for field_name, meta in six.iteritems(children_fields(msg_cls)):
            child_msg_cls = tmp_cls_loader(meta.module, meta.type)
            children = []
            for child_hash in children_list[field_name]:
                child_node = node._mknode(child_msg_cls)
                child_node.load_latest(child_hash)
                child_rev = child_node.latest
                children.append(child_rev)
            assembled_children[field_name] = children
        rev = cls(branch, config_data, assembled_children)
        return rev

    def store_config(self):
        if self._config._hash in self._kv_store:
            return

        # crude serialization of config data
        blob = self._config._data.SerializeToString()
        if self.compress:
            blob = compress(blob)

        self._kv_store[self._config._hash] = blob

    @classmethod
    def load_config(cls, kv_store, msg_cls, config_hash):
        blob = kv_store[config_hash]
        if cls.compress:
            blob = decompress(blob)

        # TODO use a loader later on
        data = msg_cls()
        data.ParseFromString(blob)
        return data


def tmp_cls_loader(module_name, cls_name):
    # TODO this shall be generalized
    from voltha.protos import voltha_pb2, health_pb2, adapter_pb2, \
        logical_device_pb2, device_pb2, openflow_13_pb2, bbf_fiber_base_pb2, \
        bbf_fiber_traffic_descriptor_profile_body_pb2, \
        bbf_fiber_tcont_body_pb2, bbf_fiber_gemport_body_pb2, \
        bbf_fiber_multicast_gemport_body_pb2, \
        bbf_fiber_multicast_distribution_set_body_pb2, \
        omci_mib_db_pb2, \
        omci_alarm_db_pb2
    return getattr(locals()[module_name], cls_name)
