# Copyright (c) 2013 Mirantis 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.

from oslo_serialization import jsonutils
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
from sqlalchemy.ext import mutable


class JsonEncoded(sa.TypeDecorator):
    """Represents an immutable structure as a json-encoded string."""

    impl = sa.Text

    def process_bind_param(self, value, dialect):
        if value is not None:
            value = jsonutils.dumps(value)
        return value

    def process_result_value(self, value, dialect):
        if value is not None:
            value = jsonutils.loads(value)
        return value


class MutableDict(mutable.Mutable, dict):
    @classmethod
    def coerce(cls, key, value):
        """Convert plain dictionaries to MutableDict."""
        if not isinstance(value, MutableDict):
            if isinstance(value, dict):
                return MutableDict(value)

            # this call will raise ValueError
            return mutable.Mutable.coerce(key, value)
        else:
            return value

    def update(self, e=None, **f):
        """Detect dictionary update events and emit change events."""
        dict.update(self, e, **f)
        self.changed()

    def __setitem__(self, key, value):
        """Detect dictionary set events and emit change events."""
        dict.__setitem__(self, key, value)
        self.changed()

    def __delitem__(self, key):
        """Detect dictionary del events and emit change events."""
        dict.__delitem__(self, key)
        self.changed()


class MutableList(mutable.Mutable, list):
    @classmethod
    def coerce(cls, key, value):
        """Convert plain lists to MutableList."""
        if not isinstance(value, MutableList):
            if isinstance(value, list):
                return MutableList(value)

            # this call will raise ValueError
            return mutable.Mutable.coerce(key, value)
        else:
            return value

    def append(self, value):
        """Detect list add events and emit change events."""
        list.append(self, value)
        self.changed()

    def remove(self, value):
        """Removes an item by value and emit change events."""
        list.remove(self, value)
        self.changed()

    def __setitem__(self, key, value):
        """Detect list set events and emit change events."""
        list.__setitem__(self, key, value)
        self.changed()

    def __delitem__(self, i):
        """Detect list del events and emit change events."""
        list.__delitem__(self, i)
        self.changed()


def JsonDictType():
    """Returns an SQLAlchemy Column Type suitable to store a Json dict."""
    return MutableDict.as_mutable(JsonEncoded)


def JsonListType():
    """Returns an SQLAlchemy Column Type suitable to store a Json array."""
    return MutableList.as_mutable(JsonEncoded)


def LargeBinary():
    return sa.LargeBinary().with_variant(mysql.LONGBLOB(), 'mysql')


def LongText():
    return sa.Text().with_variant(mysql.LONGTEXT(), 'mysql')
