# Copyright 2011-2012 Nicolas Bessi (Camptocamp SA)
# Copyright 2016 Yannick Vaucher (Camptocamp SA)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, models
from odoo.exceptions import MissingError, except_orm

from . import fields as geo_fields, geo_operators

DEFAULT_EXTENT = (
    "-123164.85222423, 5574694.9538936, " "1578017.6490538, 6186191.1800898"
)


class GeoModel(models.AbstractModel):
    """Extend Base class for to allow definition of geo fields."""

    _inherit = "base"

    # Array of ash that define layer and data to use
    _georepr = []

    @api.model
    def fields_get(self, allfields=None, attributes=None):
        """Add geo_type definition for geo fields"""
        res = super().fields_get(allfields=allfields, attributes=attributes)
        for f_name in res:
            field = self._fields.get(f_name)
            if field and field.type.startswith("geo_"):
                geo_type = {
                    "type": field.type,
                    "dim": int(field.dim),
                    "srid": field.srid,
                }
                if field.compute or field.related:
                    if not field.dim:
                        geo_type["dim"] = 2
                    if not field.srid:
                        geo_type["srid"] = 3857
                res[f_name]["geo_type"] = geo_type
        return res

    @api.model
    def _get_geo_view(self):
        geo_view = self.env["ir.ui.view"].search(
            [("model", "=", self._name), ("type", "=", "geoengine")], limit=1
        )
        if not geo_view:
            raise except_orm(
                _("No GeoEngine view defined for the model %s") % self._name,
                _("Please create a view or modify view mode"),
            )
        return geo_view

    @api.model
    def fields_view_get(
        self, view_id=None, view_type="form", toolbar=False, submenu=False
    ):
        """Returns information about the available fields of the class.
        If view type == 'map' returns geographical columns available"""
        view_obj = self.env["ir.ui.view"]
        field_obj = self.env["ir.model.fields"]

        def set_field_real_name(in_tuple):
            if not in_tuple:
                return in_tuple
            name = field_obj.browse(in_tuple[0]).name
            out = (in_tuple[0], name, in_tuple[1])
            return out

        if view_type == "geoengine":
            if not view_id:
                view = self._get_geo_view()
            else:
                view = view_obj.browse(view_id)
            res = super().fields_view_get(
                view_id=view.id, view_type="form", toolbar=toolbar, submenu=submenu
            )
            res["geoengine_layers"] = {
                "backgrounds": [],
                "actives": [],
                "projection": view.projection,
                "restricted_extent": view.restricted_extent,
                "default_extent": view.default_extent or DEFAULT_EXTENT,
                "default_zoom": view.default_zoom,
            }
            for layer in view.raster_layer_ids:
                layer_dict = layer.read()[0]
                res["geoengine_layers"]["backgrounds"].append(layer_dict)
            for layer in view.vector_layer_ids:
                layer_dict = layer.read()[0]
                # get category groups for this vector layer
                if layer.geo_repr == "basic" and layer.symbol_ids:
                    layer_dict["symbols"] = layer.symbol_ids.read(
                        ["img", "fieldname", "value"]
                    )
                layer_dict["attribute_field_id"] = set_field_real_name(
                    layer_dict.get("attribute_field_id", False)
                )
                layer_dict["geo_field_id"] = set_field_real_name(
                    layer_dict.get("geo_field_id", False)
                )
                res["geoengine_layers"]["actives"].append(layer_dict)
                # adding geo column desc
                geo_f_name = layer_dict["geo_field_id"][1]
                res["fields"].update(self.fields_get([geo_f_name]))
        else:
            return super().fields_view_get(
                view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu
            )
        return res

    @api.model
    def get_edit_info_for_geo_column(self, column):
        raster_obj = self.env["geoengine.raster.layer"]

        field = self._fields.get(column)
        if not field or not isinstance(field, geo_fields.GeoField):
            raise ValueError(
                _("%s column does not exists or is not a geo field") % column
            )
        view = self._get_geo_view()
        raster = raster_obj.search(
            [("view_id", "=", view.id), ("use_to_edit", "=", True)], limit=1
        )
        if not raster:
            raster = raster_obj.search([("view_id", "=", view.id)], limit=1)
        if not raster:
            raise MissingError(_("No raster layer for view %s") % (view.name,))
        return {
            "edit_raster": raster.read()[0],
            "geo_type": field.geo_type,
            "srid": field.srid,
            "projection": view.projection,
            "restricted_extent": view.restricted_extent,
            "default_extent": view.default_extent or DEFAULT_EXTENT,
            "default_zoom": view.default_zoom,
        }

    @api.model
    def geo_search(
        self, domain=None, geo_domain=None, offset=0, limit=None, order=None
    ):
        """Perform a geo search it allows direct domain:
        geo_search(
            domain=[('name', 'ilike', 'toto']),
            geo_domain=[('the_point', 'geo_intersect',
                          myshaply_obj or mywkt or mygeojson)])

        We can also support indirect geo_domain (
           ‘geom’, ‘geo_operator’, {‘res.zip.poly’: [‘id’, ‘in’, [1,2,3]] })

        The supported operators are :
         * geo_greater
         * geo_lesser
         * geo_equal
         * geo_touch
         * geo_within
         * geo_contains
         * geo_intersect"""
        # First we do a standard search in order to apply security rules
        # and do a search on standard attributes
        # Limit and offset are managed after, we may loose a lot of performance
        # here
        domain = domain or []
        geo_domain = geo_domain or []
        return geo_operators.geo_search(
            self,
            domain=domain,
            geo_domain=geo_domain,
            offset=offset,
            limit=limit,
            order=order,
        )
