"""Implementation of the intermediary layer between Shapely and GEOS

This is layer number 2 from the list below.

1) geometric objects: the Python OO API.
2) implementation map: an abstraction that permits different backends.
3) backend: callable objects that take Shapely geometric objects as arguments 
   and, with GEOS as a backend, translate them to C data structures.
4) GEOS library: algorithms implemented in C++.

Shapely 1.2 includes a GEOS backend and it is the default.
"""

from ftools import wraps

from shapely.algorithms import cga
from shapely.coords import BoundsOp
from shapely.geos import lgeos
from shapely.linref import ProjectOp, InterpolateOp
from shapely.predicates import BinaryPredicate, UnaryPredicate
from shapely.topology import BinaryRealProperty, BinaryTopologicalOp
from shapely.topology import UnaryRealProperty, UnaryTopologicalOp

def delegated(func):
    """A delegated method raises AttributeError in the absence of backend 
    support."""
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except KeyError:
            raise AttributeError, "Method '%s' is not supported by %s" % (
                func.__name__, repr(args[0].impl))
    return wrapper

# Map geometry methods to their GEOS delegates

class BaseImpl(object):
    def __init__(self, values):
        self.map = dict(values)
    def update(self, values):
        self.map.update(values)
    def __getitem__(self, key):
        return self.map[key]

class GEOSImpl(BaseImpl):
    def __repr__(self):
        return '<GEOSImpl object: GEOS C API version %s>' % (
            lgeos.geos_capi_version,)

IMPL14 = {
    'area': (UnaryRealProperty, 'area'),
    'distance': (BinaryRealProperty, 'distance'),
    'length': (UnaryRealProperty, 'length'),
    #
    'boundary': (UnaryTopologicalOp, 'boundary'),
    'bounds': (BoundsOp, None),
    'centroid': (UnaryTopologicalOp, 'centroid'),
    'representative_point': (UnaryTopologicalOp, 'representative_point'),
    'envelope': (UnaryTopologicalOp, 'envelope'),
    'convex_hull': (UnaryTopologicalOp, 'convex_hull'),
    'buffer': (UnaryTopologicalOp, 'buffer'),
    #
    'difference': (BinaryTopologicalOp, 'difference'),
    'intersection': (BinaryTopologicalOp, 'intersection'),
    'symmetric_difference': (BinaryTopologicalOp, 'symmetric_difference'),
    'union': (BinaryTopologicalOp, 'union'),
    #
    'has_z': (UnaryPredicate, 'has_z'),
    'is_empty': (UnaryPredicate, 'is_empty'),
    'is_ring': (UnaryPredicate, 'is_ring'),
    'is_simple': (UnaryPredicate, 'is_simple'),
    'is_valid': (UnaryPredicate, 'is_valid'),
    #
    'relate': (BinaryPredicate, 'relate'),
    'contains': (BinaryPredicate, 'contains'),
    'crosses': (BinaryPredicate, 'crosses'),
    'disjoint': (BinaryPredicate, 'disjoint'),
    'equals': (BinaryPredicate, 'equals'),
    'intersects': (BinaryPredicate, 'intersects'),
    'overlaps': (BinaryPredicate, 'overlaps'),
    'touches': (BinaryPredicate, 'touches'),
    'within': (BinaryPredicate, 'within'),
    'equals_exact': (BinaryPredicate, 'equals_exact'),
    
    # First pure Python implementation
    'is_ccw': (cga.is_ccw_impl, 'is_ccw'),
    }

IMPL15 = {
    'simplify': (UnaryTopologicalOp, 'simplify'),
    'topology_preserve_simplify': 
        (UnaryTopologicalOp, 'topology_preserve_simplify'),
    'prepared_intersects': (BinaryPredicate, 'prepared_intersects'),
    'prepared_contains': (BinaryPredicate, 'prepared_contains'),
    'prepared_contains_properly': 
        (BinaryPredicate, 'prepared_contains_properly'),
    'prepared_covers': (BinaryPredicate, 'prepared_covers'),
    }

IMPL16 = {
    }

IMPL16LR = {
	'parallel_offset': (UnaryTopologicalOp, 'parallel_offset'),
	'project_normalized': (ProjectOp, 'project_normalized'),
    'project': (ProjectOp, 'project'),
    'interpolate_normalized': (InterpolateOp, 'interpolate_normalized'),
    'interpolate': (InterpolateOp, 'interpolate'),
    }

def impl_items(defs):
    return [(k, v[0](v[1])) for k, v in defs.items()]

imp = GEOSImpl(dict(impl_items(IMPL14)))
if lgeos.geos_capi_version >= (1, 5, 0):
    imp.update(impl_items(IMPL15))
if lgeos.geos_capi_version >= (1, 6, 0):
    imp.update(impl_items(IMPL16))
    if 'project' in lgeos.methods:
        imp.update(impl_items(IMPL16LR))

DefaultImplementation = imp
