# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE.  If not, see <http://www.gnu.org/licenses/>.
import openstack_api
from django.conf import settings
from django.contrib.auth import user_logged_in
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.urlresolvers import reverse
from django.db.models import CharField
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.fields import (
    GenericRelation, GenericForeignKey
)
from django.contrib.contenttypes.models import ContentType

from firewall.models import Vlan
from firewall.fields import val_alfanum
from openstack_auth.user import User
from network import operations

class EditorElement(models.Model):
    x = models.IntegerField()
    y = models.IntegerField()
    free_port_num = models.IntegerField()
    owner = models.ForeignKey(User)

    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey()

    def as_data(self):
        type = 'network' if isinstance(self.content_object, Vxlan) else 'vm'
        if type == 'network':
            id = "net-%s" % self.content_object.vni
        else:
            id = "vm-%s" % self.content_object.pk
        return {
            'name': unicode(self.content_object),
            'id': id,
            'x': self.x,
            'y': self.y,
            'free_port_num': self.free_port_num,
            'type': type,
            'description': self.content_object.description,
        }


class Vxlan(models.Model):

    """
    A virtual L2 network,
    """
    os_network_id = CharField(blank=False, max_length=100, unique=True)
    editor_elements = GenericRelation(EditorElement)

    class Meta:
        app_label = 'network'

    def __unicode__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('network.vxlan', kwargs={'vni': self.vni})

class SubnetPool(object):
    @classmethod
    def __get(cls, request):
        subnet_pools = openstack_api.neutron.subnetpool_list(request)
        subnet_pools = [sp for sp in subnet_pools if sp.name == settings.DEFAULT_SUBNETPOOL_NAME_FOR_USER]
        return subnet_pools[0] if len(subnet_pools) > 0 else None

    @classmethod
    def __create_if_not_exists(cls, request):
        default_subnet_pool = SubnetPool.__get(request)
        if default_subnet_pool is None:
            return openstack_api.neutron.subnetpool_create(
                request,
                settings.DEFAULT_SUBNETPOOL_NAME_FOR_USER,
                settings.DEFAULT_SUBNETPOOL_PREFIXES,
                default_prefixlen=settings.DEFAULT_SUBNETPOOL_PREFIX_LEN
            )
        else:
            return default_subnet_pool

    @classmethod
    def get_id(cls, request):
        return SubnetPool.__create_if_not_exists(request).id

class DefaultPublicRouter(object):
    @classmethod
    def __get_default_public_network(cls, request):
        nets = openstack_api.neutron.network_list(request)
        nets = [n for n in nets if n.name == settings.DEFAULT_PUBLIC_NETWORK_NAME]
        return nets[0] if len(nets) > 0 else None

    @classmethod
    def __get(cls, request):
        routers = openstack_api.neutron.router_list(request)
        routers = [rtr for rtr in routers if rtr.name == settings.DEFAULT_PUBLIC_ROUTER_NAME_FOR_USER]
        return routers[0] if len(routers) > 0 else None

    @classmethod
    def create_if_not_exists(cls, request, subnet_id):
        default_public_router = DefaultPublicRouter.__get(request)
        if default_public_router is None:
            default_public_router = openstack_api.neutron.router_create(
                request,
                name=settings.DEFAULT_PUBLIC_ROUTER_NAME_FOR_USER,
                external_gateway_info={
                    "network_id": DefaultPublicRouter.__get_default_public_network(request).id
                }
            )

            openstack_api.neutron.router_add_interface(
                request,
                default_public_router.id,
                subnet_id=subnet_id
            )
        return default_public_router

class DefaultPublicRoutedNet(object):
    @classmethod
    def __connect_router(cls, request, default_routed_net):
        DefaultPublicRouter.create_if_not_exists(request, default_routed_net.subnets[0].id)

    @classmethod
    def __create_network(cls, request):
        network_created = openstack_api.neutron.network_create(
            request,
            name=settings.DEFAULT_PUBLIC_ROUTED_NET_NAME_FOR_USER
        )
        try:
            # TODO: default ip version should read from SETTINGS
            subnet_created = openstack_api.neutron.subnet_create(
                request,
                network_created.id,
                ip_version=4,
                subnetpool_id=SubnetPool.get_id(request)
            )
            network_created.subnets.append(subnet_created)
        except:
            openstack_api.neutron.network_delete(request, network_created.id)
            # TODO: user friendly error handling
            raise Exception("Could not create subnet for network, deleted network.")
        return network_created

    @classmethod
    def __get(cls, request):
        nets = openstack_api.neutron.network_list_for_tenant(request, request.user.project_id)
        nets = [n for n in nets if n.name == settings.DEFAULT_PUBLIC_ROUTED_NET_NAME_FOR_USER]
        return nets[0] if len(nets) > 0 else None

    @classmethod
    def __create_if_not_exists(cls, request):
        default_routed_net = DefaultPublicRoutedNet.__get(request)
        if default_routed_net is None:
            default_routed_net = DefaultPublicRoutedNet.__create_network(request)
            DefaultPublicRoutedNet.__connect_router(request, default_routed_net)

        return default_routed_net

    @classmethod
    def get_id(cls, request):
        return DefaultPublicRoutedNet.__create_if_not_exists(request).id