import os
import math
import mapscript
from Extensions.umn_mapserver.mf_export import thuban_to_map, add_circle_symbol
from Extensions.umn_mapserver.mapfile import MF_Map
from Thuban.Model.proj import Projection
try:
    from PythonMagick import Image
    pythonMagick = True
except:
    print "Coultn't import PythonMagick.  gMapTile will call convert instead."
    pythonMagick = False

cmd = 'convert -crop 256x256+%d+%d %s/%d/model.png %s/%d/%d.%d.png'

T = 2048 # Big Tile size

# Port to Python of Google Map's javascript GMercatorClass
class Mercator (object):
    def __init__(self, zoom):
        self.tileSize = 256
        self.zoom = zoom
        self.tiles = 2 ** zoom
        self.circumference = self.tileSize * self.tiles
        self.semi = self.circumference / 2
        self.radius = self.circumference / (2 * math.pi)
        self.fE = -1.0 * self.circumference / 2.0
        self.fN = self.circumference / 2.0 

    def longToX (self, degrees):
        longitude = math.radians(degrees + 180)
        return (self.radius * longitude)

    def latToY (self, degrees):
        latitude = math.radians(degrees)
        y = self.radius/2.0 * math.log((1.0 + math.sin(latitude)) /
                                           (1.0 - math.sin(latitude)))
        return self.fN - y

    def xToLong (self, x):
        longRadians = x/self.radius
        longDegrees = math.degrees(longRadians) - 180
        rotations = math.floor((longDegrees + 180)/360)
        longitude = longDegrees - (rotations * 360)
        return longitude

    def yToLat (self, y):
        y = self.fN - y
        latitude = ((math.pi / 2) -
                    (2 * math.atan(math.exp(-1.0 * y / self.radius))))
        return math.degrees(latitude)

def generate (mf, pixtents, zoom, folder):
    """ Generate an image of the map, and then
        Chop up in to 256x256px tiles.  'mf' is the mapObj object completely
        configured except for its size and extents.
        'pixtents' is the extents to be shown, in projected (pixel)
        coordinates.
        'zoom' is the zoom level for the image.
        'folder' is the folder to save the tiles. """
    c = Mercator(zoom)
    extents = [pixtents[0] - c.semi, c.semi - pixtents[1],
               pixtents[2] - c.semi, c.semi - pixtents[3]]
    mf.set_size (pixtents[2] - pixtents[0], pixtents[1] - pixtents[3])
    mf.set_extent (extents)
    img = mf._mf_map.draw()
    png = img.save(folder+'/%d/model.png' % (zoom,))
    xTile = pixtents[0] // 256
    yTile = pixtents[3] // 256
    width = pixtents[2] - pixtents[0]
    height = pixtents[1] - pixtents[3]
    i = xTile
    x = 0
    if pythonMagick:
        img = Image(str(folder+"/%d/model.png" % (zoom,)))
    while x < width:
        y = 0
        j = yTile
        while y < height:
            if pythonMagick:
                img2 = Image(img)
                img2.crop ('256x256+%d+%d' % (x, y))
                img2.write (str(folder+"/%d/%d.%d.png" % (zoom, i, j)))
            else:
                c = cmd % (x, y, folder, zoom, folder, zoom, i, j)
                os.system(c)
            y += 256
            j += 1
        x += 256
        i += 1

def export (context, minZoom, maxZoom, dataFolder, filename=None):
    """ Use the Thuban context to render the current map to gMapTiles.
        All zoom levels between 'minZoom' and 'maxZoom' will be generated.
        Tiles will be saved to 'dataFolder'.
        If 'filename' is not None, the map will be generated from this mapfile,
        instead of using 'context' (for testing only). """
    if filename is None:
        mf = MF_Map(mapscript.mapObj(""))
        mf.set_size (60, 50) # Set some size for thuban_to_map not to fail
        add_circle_symbol (mf)
        thuban_to_map (context, mf)
    else:
        mf = MF_Map(mapscript.mapObj(filename))
    # We need all layers to have a projection, assume that they're
    # using un-projected (lat/long) coordinates.  Probably an exception
    # should be raised instead
    for lindex in range(mf._mf_map.numlayers):
        l = mf._mf_map.getLayer(lindex)
        if l.getProjection() == '':
            l.setProjection("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
    mf.set_imagetype ('png')
    mf._mf_map.imagecolor.setRGB(234, 234, 234)
    mf._mf_map.transparent = 1
    of = mapscript.outputFormatObj("GD/png")
    of.transparent = 1
    mf._mf_map.setOutputFormat (of)

    if filename is None:
        extents = context.mainwindow.canvas.map.BoundingBox() # Unprojected
    else:
        extents = mf.get_extent().get_rect()
    if extents is None:
        return
    minx, miny, maxx, maxy = extents
    for zoom in range(minZoom, maxZoom + 1):
        try:
            os.makedirs (os.path.join(dataFolder, str(zoom)))
        except OSError:
            pass # Lets assume that the directory already exists
        coord = Mercator(zoom)
        gMercator = ["proj=merc", "a=%f"%coord.radius, "b=%f"%coord.radius]
        mf.set_projection (Projection(gMercator))
        mminx = ((int(coord.longToX(minx))) // 256) * 256 # Projected (pixels)
        mmaxx = ((int(coord.longToX(maxx)) + 255)// 256) * 256 - 1
        mmaxy = ((int(coord.latToY(maxy))) // 256) * 256
        mminy = ((int(coord.latToY(miny)) + 255)// 256) * 256 - 1
        width = mmaxx - mminx
        height = mminy - mmaxy
        width = max (width, 1)
        height = max (height, 1)
        if width <= T and height < T:
            pixtents = [mminx, mminy, mmaxx, mmaxy]
            generate (mf, pixtents, zoom, dataFolder)
        else:
            x = mminx
            while x < mmaxx:
                y = mmaxy
                while y < mminy:
                    pixtents = [x, min(y + T - 1, mminy), min(x + T - 1, mmaxx), y]
                    generate (mf, pixtents, zoom, dataFolder)
                    y += T
                x += T

if __name__ == '__main__':
    import sys
    if len(sys.argv) <= 1:
        print "-----\nUsage: python exporter.py <mapfile.map>"
    else:
        # Say, let's export only for zoom level seven.  I like seven.
        export (None, 7, 7, 'tiles', sys.argv[-1])

