#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys
import tempfile

from nose.tools import eq_, raises
from nose.plugins.skip import SkipTest

import mapnik

from .utilities import execution_path, run_all

PYTHON3 = sys.version_info[0] == 3


def setup():
    # All of the paths used are relative, if we run the tests
    # from another directory we need to chdir()
    os.chdir(execution_path('.'))


def test_simplest_render():
    m = mapnik.Map(256, 256)
    im = mapnik.Image(m.width, m.height)
    eq_(im.painted(), False)
    eq_(im.is_solid(), True)
    mapnik.render(m, im)
    eq_(im.painted(), False)
    eq_(im.is_solid(), True)
    s = im.tostring()
    if PYTHON3:
        eq_(s, 256 * 256 * b'\x00\x00\x00\x00')
    else:
        eq_(s, 256 * 256 * '\x00\x00\x00\x00')


def test_render_image_to_string():
    im = mapnik.Image(256, 256)
    im.fill(mapnik.Color('black'))
    eq_(im.painted(), False)
    eq_(im.is_solid(), True)
    s = im.tostring()
    if PYTHON3:
        eq_(s, 256 * 256 * b'\x00\x00\x00\xff')
    else:
        eq_(s, 256 * 256 * '\x00\x00\x00\xff')


def test_non_solid_image():
    im = mapnik.Image(256, 256)
    im.fill(mapnik.Color('black'))
    eq_(im.painted(), False)
    eq_(im.is_solid(), True)
    # set one pixel to a different color
    im.set_pixel(0, 0, mapnik.Color('white'))
    eq_(im.painted(), False)
    eq_(im.is_solid(), False)


def test_non_solid_image_view():
    im = mapnik.Image(256, 256)
    im.fill(mapnik.Color('black'))
    view = im.view(0, 0, 256, 256)
    eq_(view.is_solid(), True)
    # set one pixel to a different color
    im.set_pixel(0, 0, mapnik.Color('white'))
    eq_(im.is_solid(), False)
    # view, since it is the exact dimensions of the image
    # should also be non-solid
    eq_(view.is_solid(), False)
    # but not a view that excludes the single diff pixel
    view2 = im.view(1, 1, 256, 256)
    eq_(view2.is_solid(), True)


def test_setting_alpha():
    w, h = 256, 256
    im1 = mapnik.Image(w, h)
    # white, half transparent
    c1 = mapnik.Color('rgba(255,255,255,.5)')
    im1.fill(c1)
    eq_(im1.painted(), False)
    eq_(im1.is_solid(), True)
    # pure white
    im2 = mapnik.Image(w, h)
    c2 = mapnik.Color('rgba(255,255,255,1)')
    im2.fill(c2)
    im2.apply_opacity(c1.a / 255.0)
    eq_(im2.painted(), False)
    eq_(im2.is_solid(), True)
    eq_(len(im1.tostring('png32')), len(im2.tostring('png32')))


def test_render_image_to_file():
    im = mapnik.Image(256, 256)
    im.fill(mapnik.Color('black'))
    if mapnik.has_jpeg():
        im.save('test.jpg')
    im.save('test.png', 'png')
    if os.path.exists('test.jpg'):
        os.remove('test.jpg')
    else:
        return False
    if os.path.exists('test.png'):
        os.remove('test.png')
    else:
        return False


def get_paired_images(w, h, mapfile):
    tmp_map = 'tmp_map.xml'
    m = mapnik.Map(w, h)
    mapnik.load_map(m, mapfile)
    im = mapnik.Image(w, h)
    m.zoom_all()
    mapnik.render(m, im)
    mapnik.save_map(m, tmp_map)
    m2 = mapnik.Map(w, h)
    mapnik.load_map(m2, tmp_map)
    im2 = mapnik.Image(w, h)
    m2.zoom_all()
    mapnik.render(m2, im2)
    os.remove(tmp_map)
    return im, im2


def test_render_from_serialization():
    if not os.path.exists('../data/good_maps/building_symbolizer.xml') or not os.path.exists('../data/good_maps/polygon_symbolizer.xml'):
        raise SkipTest

    try:
        im, im2 = get_paired_images(
            100, 100, '../data/good_maps/building_symbolizer.xml')
        eq_(im.tostring('png32'), im2.tostring('png32'))

        im, im2 = get_paired_images(
            100, 100, '../data/good_maps/polygon_symbolizer.xml')
        eq_(im.tostring('png32'), im2.tostring('png32'))
    except RuntimeError as e:
        # only test datasources that we have installed
        if not 'Could not create datasource' in str(e):
            raise RuntimeError(e)


def test_render_points():
    if not mapnik.has_cairo():
        return
    # create and populate point datasource (WGS84 lat-lon coordinates)
    ds = mapnik.MemoryDatasource()
    context = mapnik.Context()
    context.push('Name')
    f = mapnik.Feature(context, 1)
    f['Name'] = 'Westernmost Point'
    f.geometry = mapnik.Geometry.from_wkt('POINT (142.48 -38.38)')
    ds.add_feature(f)

    f = mapnik.Feature(context, 2)
    f['Name'] = 'Southernmost Point'
    f.geometry = mapnik.Geometry.from_wkt('POINT (143.10 -38.60)')
    ds.add_feature(f)

    # create layer/rule/style
    s = mapnik.Style()
    r = mapnik.Rule()
    symb = mapnik.PointSymbolizer()
    symb.allow_overlap = True
    r.symbols.append(symb)
    s.rules.append(r)
    lyr = mapnik.Layer(
        'Places',
        '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')
    lyr.datasource = ds
    lyr.styles.append('places_labels')
    # latlon bounding box corners
    ul_lonlat = mapnik.Coord(142.30, -38.20)
    lr_lonlat = mapnik.Coord(143.40, -38.80)
    # render for different projections
    projs = {
        'google': '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over',
        'latlon': '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs',
        'merc': '+proj=merc +datum=WGS84 +k=1.0 +units=m +over +no_defs',
        'utm': '+proj=utm +zone=54 +datum=WGS84'
    }
    for projdescr in projs:
        m = mapnik.Map(1000, 500, projs[projdescr])
        m.append_style('places_labels', s)
        m.layers.append(lyr)
        dest_proj = mapnik.Projection(projs[projdescr])
        src_proj = mapnik.Projection('+init=epsg:4326')
        tr = mapnik.ProjTransform(src_proj, dest_proj)
        m.zoom_to_box(tr.forward(mapnik.Box2d(ul_lonlat, lr_lonlat)))
        # Render to SVG so that it can be checked how many points are there
        # with string comparison
        svg_file = os.path.join(
            tempfile.gettempdir(),
            'mapnik-render-points-%s.svg' %
            projdescr)
        mapnik.render_to_file(m, svg_file)
        num_points_present = len(list(ds.all_features()))
        with open(svg_file, 'r') as f:
            svg = f.read()
        num_points_rendered = svg.count('<image ')
        eq_(
            num_points_present,
            num_points_rendered,
            "Not all points were rendered (%d instead of %d) at projection %s" %
            (num_points_rendered,
             num_points_present,
             projdescr))


@raises(RuntimeError)
def test_render_with_scale_factor_zero_throws():
    m = mapnik.Map(256, 256)
    im = mapnik.Image(256, 256)
    mapnik.render(m, im, 0.0)


def test_render_with_detector():
    ds = mapnik.MemoryDatasource()
    context = mapnik.Context()
    geojson = '{ "type": "Feature", "geometry": { "type": "Point", "coordinates": [ 0, 0 ] } }'
    ds.add_feature(mapnik.Feature.from_geojson(geojson, context))
    s = mapnik.Style()
    r = mapnik.Rule()
    lyr = mapnik.Layer('point')
    lyr.datasource = ds
    lyr.styles.append('point')
    symb = mapnik.MarkersSymbolizer()
    symb.allow_overlap = False
    r.symbols.append(symb)
    s.rules.append(r)
    m = mapnik.Map(256, 256)
    m.append_style('point', s)
    m.layers.append(lyr)
    m.zoom_to_box(mapnik.Box2d(-180, -85, 180, 85))
    im = mapnik.Image(256, 256)
    mapnik.render(m, im)
    expected_file = './images/support/marker-in-center.png'
    actual_file = '/tmp/' + os.path.basename(expected_file)
    # im.save(expected_file,'png8')
    im.save(actual_file, 'png8')
    actual = mapnik.Image.open(expected_file)
    expected = mapnik.Image.open(expected_file)
    eq_(actual.tostring('png32'),
        expected.tostring('png32'),
        'failed comparing actual (%s) and expected (%s)' % (actual_file,
                                                            expected_file))
    # now render will a collision detector that should
    # block out the placement of this point
    detector = mapnik.LabelCollisionDetector(m)
    eq_(detector.extent(), mapnik.Box2d(-0.0, -0.0, m.width, m.height))
    eq_(detector.extent(), mapnik.Box2d(-0.0, -0.0, 256.0, 256.0))
    eq_(detector.boxes(), [])
    detector.insert(detector.extent())
    eq_(detector.boxes(), [detector.extent()])
    im2 = mapnik.Image(256, 256)
    mapnik.render_with_detector(m, im2, detector)
    expected_file_collision = './images/support/marker-in-center-not-placed.png'
    # im2.save(expected_file_collision,'png8')
    actual_file = '/tmp/' + os.path.basename(expected_file_collision)
    im2.save(actual_file, 'png8')


if 'shape' in mapnik.DatasourceCache.plugin_names():

    def test_render_with_scale_factor():
        if not os.path.exists('../data/good_maps/marker-text-line.xml'):
            raise SkipTest

        m = mapnik.Map(256, 256)
        mapnik.load_map(m, '../data/good_maps/marker-text-line.xml')
        m.zoom_all()
        sizes = [.00001, .005, .1, .899, 1, 1.5, 2, 5, 10, 100]
        for size in sizes:
            im = mapnik.Image(256, 256)
            mapnik.render(m, im, size)
            expected_file = './images/support/marker-text-line-scale-factor-%s.png' % size
            actual_file = '/tmp/' + os.path.basename(expected_file)
            im.save(actual_file, 'png32')
            if os.environ.get('UPDATE'):
                im.save(expected_file, 'png32')
            # we save and re-open here so both png8 images are ready as full
            # color png
            actual = mapnik.Image.open(actual_file)
            expected = mapnik.Image.open(expected_file)
            eq_(actual.tostring('png32'),
                expected.tostring('png32'),
                'failed comparing actual (%s) and expected (%s)' % (actual_file,
                                                                    expected_file))

if __name__ == "__main__":
    setup()
    exit(run_all(eval(x) for x in dir() if x.startswith("test_")))
