# Copyright (c) 2015 OpenStack Foundation.
#
# 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.

"""flavors: an implementation of the flavor management storage
controller for sqlalchemy.

"""

import oslo_db.exception
import sqlalchemy as sa

from zaqar.storage import base
from zaqar.storage import errors
from zaqar.storage.sqlalchemy import tables
from zaqar.storage.sqlalchemy import utils


class FlavorsController(base.FlavorsBase):

    def __init__(self, *args, **kwargs):
        super(FlavorsController, self).__init__(*args, **kwargs)
        self._pools_ctrl = self.driver.pools_controller

    @utils.raises_conn_error
    def list(self, project=None, marker=None, limit=10, detailed=False):
        marker = marker or ''

        # TODO(cpp-cabrera): optimization - limit the columns returned
        # when detailed=False by specifying them in the select()
        # clause
        stmt = sa.sql.select([tables.Flavors]).where(
            sa.and_(tables.Flavors.c.name > marker,
                    tables.Flavors.c.project == project)
        )

        if limit > 0:
            stmt = stmt.limit(limit)
        cursor = self.driver.run(stmt)

        marker_name = {}

        def it():
            for cur in cursor:
                marker_name['next'] = cur[0]
                yield _normalize(cur, detailed=detailed)

        yield it()
        yield marker_name and marker_name['next']

    @utils.raises_conn_error
    def get(self, name, project=None, detailed=False):
        stmt = sa.sql.select([tables.Flavors]).where(
            sa.and_(tables.Flavors.c.name == name,
                    tables.Flavors.c.project == project)
        )

        flavor = self.driver.run(stmt).fetchone()
        if flavor is None:
            raise errors.FlavorDoesNotExist(name)

        return _normalize(flavor, detailed)

    @utils.raises_conn_error
    def create(self, name, pool_group=None, project=None, capabilities=None):
        cap = None if capabilities is None else utils.json_encode(capabilities)

        try:
            if pool_group is not None:
                stmt = sa.sql.expression.insert(tables.Flavors).values(
                    name=name, pool_group=pool_group, project=project,
                    capabilities=cap
                )
            else:
                stmt = sa.sql.expression.insert(tables.Flavors).values(
                    name=name, project=project,
                    capabilities=cap
                )
            self.driver.run(stmt)
        except oslo_db.exception.DBDuplicateEntry:
            # NOTE(gengchc2): If you do not use the removal group scheme to
            # configure flavor, pool_group can be None..
            if pool_group is not None:
                flavor_obj = {}
                flavor_obj["pool_group"] = pool_group
                if not list(self._pools_ctrl.get_pools_by_flavor(flavor_obj)):
                    raise errors.PoolGroupDoesNotExist(pool_group)

            # TODO(flaper87): merge update/create into a single
            # method with introduction of upsert
            self.update(name, pool_group=pool_group,
                        project=project,
                        capabilities=capabilities)

    @utils.raises_conn_error
    def exists(self, name, project=None):
        stmt = sa.sql.select([tables.Flavors.c.name]).where(
            sa.and_(tables.Flavors.c.name == name,
                    tables.Flavors.c.project == project)
        ).limit(1)
        return self.driver.run(stmt).fetchone() is not None

    @utils.raises_conn_error
    def update(self, name, project=None, pool_group=None, capabilities=None):
        fields = {}

        if capabilities is not None:
            fields['capabilities'] = capabilities

        if pool_group is not None:
            fields['pool_group'] = pool_group

        assert fields, '`pool_group` or `capabilities` not found in kwargs'
        if 'capabilities' in fields:
            fields['capabilities'] = utils.json_encode(fields['capabilities'])

        stmt = sa.sql.update(tables.Flavors).where(
            sa.and_(tables.Flavors.c.name == name,
                    tables.Flavors.c.project == project)).values(**fields)

        res = self.driver.run(stmt)
        if res.rowcount == 0:
            raise errors.FlavorDoesNotExist(name)

    @utils.raises_conn_error
    def delete(self, name, project=None):
        stmt = sa.sql.expression.delete(tables.Flavors).where(
            sa.and_(tables.Flavors.c.name == name,
                    tables.Flavors.c.project == project)
        )
        self.driver.run(stmt)

    @utils.raises_conn_error
    def drop_all(self):
        stmt = sa.sql.expression.delete(tables.Flavors)
        self.driver.run(stmt)


def _normalize(flavor, detailed=False):
    ret = {
        'name': flavor[0],
        'pool_group': flavor[2],
    }

    if detailed:
        capabilities = flavor[3]
        ret['capabilities'] = (utils.json_decode(capabilities)
                               if capabilities else {})

    return ret
