import datetime
from functools import wraps
import threading
import tarfile
import shutil
import os
import math
import json
import pytz
import shapely
from shapely.geometry import Point
import geoalchemy2.shape
import yandex.maps.geolib3 as geolib
from maps.wikimap.mapspro.libs.python import common

import six
from six.moves import cPickle
if six.PY3:
    buffer = memoryview


ARCHIVE_SUFFIX = '.tar.gz'


def require(condition, exception):
    if not condition:
        raise exception


def utcnow():
    """ Returns datetime object with UTC timezone.
    """
    return pytz.utc.localize(datetime.datetime.utcnow())


def mercator_to_geodetic(x, y):
    point = geolib.mercator_to_geo(geolib.Point2(x, y))
    return (point.x, point.y)


def geodetic_to_mercator(x, y):
    point = geolib.geo_to_mercator(geolib.Point2(x, y))
    return (point.x, point.y)


def mercator_distance_ratio(latitude):
    '''Latitude must be in degrees'''
    return math.cos(math.radians(latitude))


def geojson_to_mercator_wkb(geojson):
    return common.geojsonstr_to_mercator_wkb(str(geojson))


def mercator_wkb_to_geojson(wkb):
    if isinstance(wkb, buffer):
        wkb = str(wkb)
    geojson_str = common.mercator_wkb_to_geojsonstr(wkb)
    return json.loads(geojson_str)


def geoalchemy_to_geojson(element):
    geom = geoalchemy2.shape.to_shape(element)
    return mercator_wkb_to_geojson(shapely.wkb.dumps(geom))


def bbox(geoms):
    assert(len(geoms) > 0)
    ret = geoms[0].bounds
    for geom in geoms[1:]:
        bbox = geom.bounds
        ret = (min(ret[0], bbox[0]), min(ret[1], bbox[1]),
               max(ret[2], bbox[2]), max(ret[3], bbox[3]))
    return (Point(ret[0], ret[1]), Point(ret[2], ret[3]))


def set_search_path(cursor, schema):
    cursor.execute("SET search_path TO %s" % schema)


def drop_schema(cursor, schema):
    cursor.execute("DROP SCHEMA %s CASCADE" % schema)


def geometry_column_exists(cursor, table):
    cursor.execute("""SELECT column_name FROM information_schema.columns
                      WHERE table_name = '%s' AND
                      table_schema=current_schema AND
                      udt_name='geometry'""" % (table))
    res = cursor.fetchall()
    return len(res) != 0


def schema_tables(schema, cursor):
    cursor.execute("SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname='%s'" % schema)
    rows = cursor.fetchall()
    return [row[0] for row in rows]


def string_to_bool(value):
    return value.lower() in ['1', 'true']


class ConnParams:
    def __init__(self, host, port, user, password, dbname, sslmode='', options='', masterOnly=False):
        self.host = host
        self.port = int(port)
        self.user = user
        self.password = password
        self.dbname = dbname
        self.sslmode = sslmode
        self.options = options
        self.masterOnly = masterOnly

    @classmethod
    def from_xml(cls, xml_el, **kwargs):
        attrs = dict((k if k != 'pass' else 'password', v)
                     for k, v in xml_el.attrib.items())
        attrs.update(kwargs)
        return cls(**attrs)

    def connstring(self):
        ret = "host=%(host)s port=%(port)s user=%(user)s\
            password=%(password)s dbname=%(dbname)s" \
            % self.__dict__
        if self.sslmode:
            ret += " sslmode=%(sslmode)s" % self.__dict__
        if self.options:
            ret += " options=%(options)s" % self.__dict__
        return ret

    def pgpool3_connstring(self):
        ret = "user=%(user)s password=%(password)s dbname=%(dbname)s" % self.__dict__
        if self.sslmode:
            ret += " sslmode=%(sslmode)s" % self.__dict__
        if self.options:
            ret += " options=%(options)s" % self.__dict__
        return ret

    def pgpool3_conn_instance(self):
        return (self.host, self.port)

    def environ(self):
        return {
            'PGHOST': self.host,
            'PGPORT': str(self.port),
            'PGUSER': self.user,
            'PGPASSWORD': self.password,
            'PGDATABASE': self.dbname,
            'PGOPTIONS': self.options
        }

    def uri(self):
        return 'postgresql://%(user)s:%(password)s@%(host)s:%(port)d/%(dbname)s?options=%(options)s&sslmode=%(sslmode)s' % self.__dict__

    def template(self):
        """Returns a new ConnParams instance without host and port"""
        retval = ConnParams(**self.__dict__)
        retval.host = None
        retval.port = None
        return retval

    @classmethod
    def from_template(cls, template, hostport):
        """Returns new ConnParams instance from the template
           by adding given host and port"""
        attrs = template.__dict__
        attrs['host'] = hostport.host
        attrs['port'] = hostport.port
        return cls(**attrs)


def threadsafe_memoize(factory_fun):
    scope = {}
    lock = threading.Lock()

    @wraps(factory_fun)
    def ret(*args, **kwargs):
        args_key = (args, tuple(kwargs.items()))

        try:
            hash(args_key)
        except TypeError:
            args_key = cPickle.dumps(args_key)

        if args_key not in scope:
            with lock:
                if args_key not in scope:
                    scope[args_key] = factory_fun(*args, **kwargs)

        return scope[args_key]

    return ret


def archive(path):
    archive = path + ARCHIVE_SUFFIX
    tf = tarfile.open(archive, mode='w:gz')

    if os.path.isdir(path):
        for file in os.listdir(path):
            tf.add(os.path.join(path, file), file)
        shutil.rmtree(path)
    else:
        tf.add(path, os.path.basename(path))
        os.remove(path)

    tf.close()


def archive_files(dir):
    for sub in os.listdir(dir):
        archive(os.path.join(dir, sub))
