#  Copyright (C) 2014  Statoil ASA, Norway.
#
#  The file 'layer.py' is part of ERT - Ensemble based Reservoir Tool.
#
#  ERT 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.
#
#  ERT 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 at <http://www.gnu.org/licenses/gpl.html>
#  for more details.

import ctypes

from ecl.grid import EclGrid
from cwrap import BaseCClass
from ecl.util.util import monkey_the_camel
from ecl.util.util import IntVector
from ecl import EclPrototype


class Layer(BaseCClass):
    TYPE_NAME = "layer"
    _alloc              = EclPrototype("void* layer_alloc(int,  int)", bind=False)
    _copy               = EclPrototype("void  layer_memcpy(layer, layer)")
    _free               = EclPrototype("void  layer_free(layer)")
    _get_nx             = EclPrototype("int   layer_get_nx(layer)")
    _get_ny             = EclPrototype("int   layer_get_ny(layer)")
    _set_cell           = EclPrototype("void  layer_iset_cell_value(layer, int, int, int)")
    _get_cell           = EclPrototype("int   layer_iget_cell_value(layer, int, int)")
    _get_bottom_barrier = EclPrototype("bool  layer_iget_bottom_barrier(layer, int, int)")
    _get_left_barrier   = EclPrototype("bool  layer_iget_left_barrier(layer, int, int)")
    _cell_contact       = EclPrototype("bool  layer_cell_contact(layer, int, int, int, int)")
    _add_barrier        = EclPrototype("void  layer_add_barrier(layer, int, int)")
    _add_ijbarrier      = EclPrototype("void  layer_add_ijbarrier(layer, int, int, int, int)")
    _add_interp_barrier = EclPrototype("void  layer_add_interp_barrier(layer, int, int)")
    _clear_cells        = EclPrototype("void  layer_clear_cells(layer)")
    _assign             = EclPrototype("void  layer_assign(layer, int)")
    _cell_sum           = EclPrototype("int   layer_get_cell_sum(layer)")
    _update_connected   = EclPrototype("void  layer_update_connected_cells(layer,int,int,int,int)")
    _cells_equal        = EclPrototype("void  layer_cells_equal(layer, int,int_vector,int_vector)")
    _count_equal        = EclPrototype("int   layer_count_equal(layer, int)")
    _active_cell        = EclPrototype("bool  layer_iget_active(layer, int,int)")
    _update_active      = EclPrototype("bool  layer_update_active(layer, ecl_grid, int)")

    def __init__(self, nx, ny):
        c_ptr = self._alloc(nx, ny)
        if c_ptr:
            super(Layer, self).__init__(c_ptr)
        else:
            raise ValueError("Invalid input - no Layer object created")

    @classmethod
    def copy(cls, src):
        layer = Layer(src.getNX(), src.getNY())
        layer._copy(layer, src)
        return layer


    def _assert_ij(self, i,j):
        if i < 0 or i >= self.getNX():
            raise ValueError("Invalid layer i:%d" % i)

        if j < 0 or j >= self.getNY():
            raise ValueError("Invalid layer j:%d" % j)


    def __unpack_index(self, index):
        try:
            (i,j) = index
        except TypeError:
            raise ValueError("Index:%s is invalid - must have two integers" % str(index))

        self._assert_ij(i,j)

        return (i,j)


    def __setitem__(self, index, value):
        (i,j) = self.__unpack_index(index)
        self._set_cell(i, j, value)

    def active_cell(self, i,j):
        self._assert_ij(i,j)
        return self._active_cell(i, j)


    def update_active(self, grid, k):
        if grid.getNX() != self.getNX():
            raise ValueError("NX dimension mismatch. Grid:%d  layer:%d" % (grid.getNX(), self.getNX()))

        if grid.getNY() != self.getNY():
            raise ValueError("NY dimension mismatch. Grid:%d  layer:%d" % (grid.getNY(), self.getNY()))

        if k >= grid.getNZ():
            raise ValueError("K value invalid: Grid range [0,%d)" % grid.getNZ())

        self._update_active(grid, k)


    def __getitem__(self, index):
        (i,j) = self.__unpack_index(index)
        return self._get_cell(i, j)

    def bottom_barrier(self, i,j):
        self._assert_ij(i,j)
        return self._get_bottom_barrier(i, j)

    def left_barrier(self, i,j):
        self._assert_ij(i,j)
        return self._get_left_barrier(i, j)

    def get_nx(self):
        return self._get_nx()

    @property
    def nx(self):
        return self._get_nx()

    def get_ny(self):
        return self._get_ny()

    @property
    def ny(self):
        return self._get_ny()

    def free(self):
        self._free()

    def cell_contact(self, p1, p2):
        i1,j1 = p1
        i2,j2 = p2

        if not 0 <= i1 < self.getNX():
            raise IndexError("Invalid i1:%d" % i1)

        if not 0 <= i2 < self.getNX():
            raise IndexError("Invalid i2:%d" % i2)

        if not 0 <= j1 < self.getNY():
            raise IndexError("Invalid i1:%d" % j1)

        if not 0 <= j2 < self.getNY():
            raise IndexError("Invalid i2:%d" % j2)

        return self._cell_contact(i1, j1, i2, j2)


    def add_interp_barrier(self, c1, c2):
        self._add_interp_barrier(c1, c2)


    def add_polyline_barrier(self, polyline, grid, k):
        if len(polyline) > 1:
            for i in range(len(polyline) - 1):
                x1,y1 = polyline[i]
                x2,y2 = polyline[i + 1]

                c1 = grid.findCellCornerXY(x1, y1, k)
                c2 = grid.findCellCornerXY(x2, y2, k)

                self.addInterpBarrier(c1, c2)


    def add_fault_barrier(self, fault, K, link_segments=True):
        fault_layer = fault[K]
        num_lines = len(fault_layer)
        for index, fault_line in enumerate(fault_layer):
            for segment in fault_line:
                c1, c2 = segment.getCorners()
                self._add_barrier(c1, c2)

            if index < num_lines - 1:
                next_line = fault_layer[index + 1]
                next_segment = next_line[0]
                next_c1, next_c2 = next_segment.getCorners()

                if link_segments:
                    self.addInterpBarrier(c2, next_c1)


    def add_ij_barrier(self, ij_list):
        if len(ij_list) < 2:
            raise ValueError("Must have at least two (i,j) points")

        nx = self.getNX()
        ny = self.getNY()
        p1 = ij_list[0]
        i1,j1 = p1
        for p2 in ij_list[1:]:
            i2,j2 = p2
            if i1 == i2 or j1 == j2:
                if not 0 <= i2 <= nx:
                    raise ValueError("i value:%d invalid. Valid range: [0,%d] " % (i1, i2))

                if not 0 <= j2 <= ny:
                    raise ValueError("i value:%d invalid. Valid range: [0,%d] " % (j1, j2))

                self._add_ijbarrier(i1, j1, i2, j2)
                p1 = p2
                i1,j1 = p1
            else:
                raise ValueError("Must have i1 == i2 or j1 == j2")


    def cell_sum(self):
        return self._cell_sum()

    def clear_cells(self):
        """
        Will reset all cell and edge values to zero. Barriers will be left
        unchanged.
        """
        self._clear_cells()


    def assign(self, value):
        """
        Will set the cell value to @value in all cells. Barriers will not be changed
        """
        self._assign(value)

    def update_connected(self, ij, new_value, org_value=None):
        """
        Will update cell value of all cells in contact with cell ij to the
        value @new_value. If org_value is not supplied, the current
        value in cell ij is used.
        """
        if org_value is None:
            org_value = self[ij]

        if self[ij] == org_value:
            self._update_connected(ij[0], ij[1], org_value, new_value)
        else:
            raise ValueError("Cell %s is not equal to %d \n" % (ij, org_value))


    def cells_equal(self, value):
        """
        Will return a list [(i1,j1),(i2,j2), ...(in,jn)] of all cells with value @value.
        """
        i_list = IntVector()
        j_list = IntVector()
        self._cells_equal(value, i_list, j_list)
        ij_list= []
        for (i,j) in zip(i_list, j_list):
            ij_list.append((i,j))
        return ij_list


    def count_equal(self, value):
        return self._count_equal(value)



monkey_the_camel(Layer, 'activeCell', Layer.active_cell)
monkey_the_camel(Layer, 'updateActive', Layer.update_active)
monkey_the_camel(Layer, 'bottomBarrier', Layer.bottom_barrier)
monkey_the_camel(Layer, 'leftBarrier', Layer.left_barrier)
monkey_the_camel(Layer, 'getNX', Layer.get_nx)
monkey_the_camel(Layer, 'getNY', Layer.get_ny)
monkey_the_camel(Layer, 'cellContact', Layer.cell_contact)
monkey_the_camel(Layer, 'addInterpBarrier', Layer.add_interp_barrier)
monkey_the_camel(Layer, 'addPolylineBarrier', Layer.add_polyline_barrier)
monkey_the_camel(Layer, 'addFaultBarrier', Layer.add_fault_barrier)
monkey_the_camel(Layer, 'addIJBarrier', Layer.add_ij_barrier)
monkey_the_camel(Layer, 'cellSum', Layer.cell_sum)
monkey_the_camel(Layer, 'clearCells', Layer.clear_cells)
monkey_the_camel(Layer, 'updateConnected', Layer.update_connected)
monkey_the_camel(Layer, 'cellsEqual', Layer.cells_equal)
monkey_the_camel(Layer, 'countEqual', Layer.count_equal)
