""" Provides some helper functions for test.
"""
import random
import tempfile
import os
import sys
from textwrap import dedent
import osmium
from datetime import datetime

if sys.version_info[0] == 3:
    from datetime import timezone

    def mkdate(*args):
        return datetime(*args, tzinfo=timezone.utc)
else:
    def mkdate(*args):
        return datetime(*args)


def _complete_object(o):
    """Takes a hash with an incomplete OSM object description and returns a
       complete one.
    """
    if o['type'] == 'C':
        ret = { 'created_at' : "2005-04-09T19:54:13Z",
                'num_changes' : 2, 'closed_at' : "2005-04-09T20:54:39Z",
                'open' : "false", 'min_lon' : -0.1465242,
                'min_lat' : 51.5288506, 'max_lon' : -0.1464925,
                'max_lat' : 51.5288620, 'user' : "Steve", 'uid' : "1",
                'tags' : None
        }
    else:
        ret = { 'version' : '1', 'timestamp': "2012-05-01T15:06:20Z",
                'changeset' : "11470653", 'uid' : "122294", 'user' : "foo",
                'tags' : {}
        }
    ret.update(o)
    if ret['type'] == 'N':
        if 'lat' not in ret:
            ret['lat'] = random.random()*180 - 90
        if 'lon' not in ret:
            ret['lon'] = random.random()*360 - 180
    return ret

def _write_osm_obj(fd, obj):
    if obj['type'] == 'N':
        fd.write(('<node id="%(id)d" lat="%(lat).8f" lon="%(lon).8f" version="%(version)s" timestamp="%(timestamp)s" changeset="%(changeset)s" uid="%(uid)s" user="%(user)s"'% obj).encode('utf-8'))
        if obj['tags'] is None:
            fd.write('/>\n'.encode('utf-8'))
        else:
            fd.write('>\n'.encode('utf-8'))
            for k,v in iter(obj['tags'].items()):
                fd.write(('  <tag k="%s" v="%s"/>\n' % (k, v)).encode('utf-8'))
            fd.write('</node>\n'.encode('utf-8'))
    elif obj['type'] == 'W':
        fd.write(('<way id="%(id)d" version="%(version)s" changeset="%(changeset)s" timestamp="%(timestamp)s" user="%(user)s" uid="%(uid)s">\n' % obj).encode('utf-8'))
        for nd in obj['nodes']:
            fd.write(('<nd ref="%s" />\n' % (nd,)).encode('utf-8'))
        for k,v in iter(obj['tags'].items()):
            fd.write(('  <tag k="%s" v="%s"/>\n' % (k, v)).encode('utf-8'))
        fd.write('</way>\n'.encode('utf-8'))
    elif obj['type'] == 'R':
        fd.write(('<relation id="%(id)d" version="%(version)s" changeset="%(changeset)s" timestamp="%(timestamp)s" user="%(user)s" uid="%(uid)s">\n' % obj).encode('utf-8'))
        for mem in obj['members']:
            fd.write(('  <member type="%s" ref="%s" role="%s"/>\n' % mem).encode('utf-8'))
        for k,v in iter(obj['tags'].items()):
            fd.write(('  <tag k="%s" v="%s"/>\n' % (k, v)).encode('utf-8'))
        fd.write('</relation>\n'.encode('utf-8'))
    elif obj['type'] == 'C':
        fd.write(('<changeset id="%(id)d" created_at="%(created_at)s" num_changes="%(num_changes)d" closed_at="%(closed_at)s" open="%(open)s" min_lon="%(min_lon).8f" min_lat="%(min_lat).8f" max_lon="%(max_lon).8f" max_lat="%(max_lat).8f"  user="%(user)s" uid="%(uid)s"' % obj).encode('utf-8'))
        if obj['tags'] is None:
            fd.write('/>\n'.encode('utf-8'))
        else:
            fd.write('>\n'.encode('utf-8'))
            for k,v in iter(obj['tags'].items()):
                fd.write(('  <tag k="%s" v="%s"/>\n' % (k, v)).encode('utf-8'))
            fd.write('</changeset>\n'.encode('utf-8'))



def create_osm_file(data):
    """Creates a temporary osm XML file. The data is a list of OSM objects,
       each described by a hash of attributes. Most attributes are optional
       and will be filled with sensitive values, if missing. Mandatory are
       only `type` and `id`. For ways, nodes are obligatory and for relations
       the memberlist.
    """
    data.sort(key=lambda x:('NWR'.find(x['type']), x['id']))
    with tempfile.NamedTemporaryFile(dir=tempfile.gettempdir(), suffix='.osm', delete=False) as fd:
        fname = fd.name
        fd.write("<?xml version='1.0' encoding='UTF-8'?>\n".encode('utf-8'))
        fd.write('<osm version="0.6" generator="test-pyosmium" timestamp="2014-08-26T20:22:02Z">\n'.encode('utf-8'))
        fd.write('\t<bounds minlat="-90" minlon="-180" maxlat="90" maxlon="180"/>\n'.encode('utf-8'))

        for obj in data:
            _write_osm_obj(fd, _complete_object(obj))

        fd.write('</osm>\n'.encode('utf-8'))

    return fname

def create_opl_file(data):
    with tempfile.NamedTemporaryFile(dir=tempfile.gettempdir(), suffix='.opl', delete=False) as fd:
        fname = fd.name
        fd.write(dedent(data).encode('utf-8'))
        fd.write(b'\n')

    return fname


def osmobj(kind, **args):
    ret = dict(args)
    ret['type'] = kind
    return ret

def check_repr(o):
    return not str(o).startswith('<') and not repr(o).startswith('<')

class HandlerTestBase:

    apply_locations = False
    apply_idx = 'flex_mem'

    def test_func(self):
        if isinstance(self.data, (list, tuple)):
            fn = create_osm_file(self.data)
        else:
            fn = create_opl_file(self.data)

        try:
            self.handler = self.Handler()
            self.handler.apply_file(fn, self.apply_locations, self.apply_idx)
        finally:
            os.remove(fn)

        if hasattr(self, "check_result"):
            self.check_result()


class CountingHandler(osmium.SimpleHandler):

    def __init__(self):
        super(CountingHandler, self).__init__()
        self.counts = [0, 0, 0, 0]

    def node(self, _):
        self.counts[0] += 1

    def way(self, _):
        self.counts[1] += 1

    def relation(self, _):
        self.counts[2] += 1

    def area(self, _):
        self.counts[3] += 1


class HandlerFunction(osmium.SimpleHandler):

    def __init__(self, **kwargs):
        super(HandlerFunction, self).__init__()

        for cb in ('node', 'way', 'relation', 'area'):
            if cb in kwargs:
                setattr(self, cb, kwargs[cb])

    def run(self, data, apply_locations=False, apply_idx='flex_mem'):
        if isinstance(data, (list, tuple)):
            fn = create_osm_file(data)
        else:
            fn = create_opl_file(data)

        try:
            self.apply_file(fn, apply_locations, apply_idx)
        finally:
            os.remove(fn)

