#!/usr/bin/env python


from __future__ import print_function, division
from argparse import ArgumentParser
from imagetile2 import tile_image
from PIL import Image
import os, json, sys, re, datetime, urlparse
from math import ceil, log


"""
Maybe a better name for this script is tiling or tiler as it's not particularly leaflet specific.

"""

def tiles_path_for (n):
    return n + ".tiles"

def autolink (text):
    def sub (m):
        return u'<a href="{0}">LINK</a>'.format(m.group(0))
    return re.sub(r'(?<!")https?://[\S]+(?!")', sub, text, re.I)

def parse8601 (t, fmt=None):
    """ simple 8601 parser that doesn't care about more than YMDHMS"""
    # 2016-11-16T14:13:40.379857
    m = re.search(r"(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d)T(?P<hour>\d\d):(?P<minute>\d\d):(?P<second>\d\d)", t)
    if m:
        d = m.groupdict()
        ret = datetime.datetime(int(d['year']), int(d['month']), int(d['day']), int(d['hour']), int(d['minute']), int(d['second']))
        if fmt:
            return ret.strftime(fmt)
        else:
            return ret

class tiles_wrapper (object):
    """ Image wrapper abstraction... include URL to original + caption
    """
    def __init__(self, path, url=None, text=None, tilename="z{0[z]}y{0[y]}x{0[x]}.png"):
        self.path = path
        # self.item = item
        self.url = url
        self.text = text
        self.tilename = tilename

    def get_tile_path (self, z, y, x):
        return os.path.join(self.path, self.tilename.format({'z':z,'y':y,'x':x}))

    def zoom (self):
        """ return serialized version of self """
        node = {}
        node['zoomable'] = True
        if self.text:
            node['text'] = self.text
        else:
            # autotext is a link to the url showing the basename
            _, basename = os.path.split(self.url)
            base, ext = os.path.splitext(basename)
            ext = ext[1:]
            node['text'] = u"<p class=\"caption\"><a class=\"url\" href=\"{0}\">{1}</a>".format(self.url, ext.upper())
        node['url'] = self.url
        node['image'] = self.get_tile_path(0, 0, 0)
        return node

    def zoom_recursive (self, caption, x=0, y=0, z=0, maxzoom=3):
        """ old style zoom in place -- ie render self to child nodes """
        node = {}
        node['text'] = self.text
        node['image'] = self.get_tile_path(z, y, x)
        if z < maxzoom:
            kids = []
            for r in range(2):
                for c in range(2):
                    kids.append(self.zoom_recursive(caption, (x*2)+c, (y*2)+r, z+1, maxzoom))
            node['children'] = kids
        return node

def cell_layout(items, w=2):
    i = 0
    for r in range(w):
        for c in range(w):
            if i<len(items):
                yield items[i], c, r
                i+=1

def fourup (imgs, w, h):
    print ("fourup", imgs, w, h, file=sys.stderr)
    oi = Image.new("RGBA", (w, h))
    cw = w//2
    ch = h//2
    i = 0
    for impath, c, r in cell_layout(imgs):
        if impath:
          im = Image.open(impath)
          im.thumbnail((cw, ch))
          oi.paste(im, (c*cw, r*ch))
    return oi

def split4(items):
    """ returns 4 lists where len(l) is a power of 4 """
    l = len(items)
    p = int(ceil(log(l, 4)))
    # print ("{0} items {1} {2} {3}".format(l, p, 2**p, 4**p))
    c = int((4**p)/ 4)

    # c = int(ceil(len(items) / 4))
    def el (x, c): # ensurelength
        while len(x) < c:
            x.append(None)
        return x

    ret = [items[0:c],items[c:c*2],items[c*2:c*3],items[c*3:]]
    return tuple([el(x, c) for x in ret])

def gridrender (items, basename, tilewidth=320, tileheight=320, z=0, y=0, x=0):
    """ items are now nodes proper """
    """ Takes a list of nodes and returns a new node where items are arranged in a cascade of nodes such that
    all items appear at the same (z) level -- side by side
    Uses fourup to (recursively) produce a composite image of the underlying tiles.

    """
    print ("gridrender {0} items".format(len(items)), file=sys.stderr)

    if len(items) == 1:
        x = items[0]
        if x == None:
            return None
        return x # x.zoom()
    else:
        node = {}
        node['text'] = ''
        kids = []
        for group, x2, y2 in cell_layout(split4(items)):
            kids.append(gridrender(group, basename, tilewidth, tileheight, z+1, (y*2)+y2, (x*2)+x2))
        node['children'] = [j for j in kids if j != None]
        newim = fourup([j.get("image") for j in node['children'] if j != None and j.get("image")], tilewidth, tileheight)
        node['image'] = newim
        newimpath = "{0}.z{1}y{2}x{3}.png".format(basename, z, y, x)
        newim.save(newimpath)
        node['image'] = newimpath
        print ("Created 4up image {0}".format(newimpath), file=sys.stderr)

        return node

def recursiverender (items, basename, tilewidth=320, tileheight=320, direction=3, z=0):
    node = {}
    node['text'] = ''
    # if len(items) >=1 and 'date' in items[0].item:
    #     node['text'] = items[0].item['date']
    # else:
    #     node['text'] = ''
    # node['image'] = ''
    node['children'] = cc = [None, None, None, None]
    ai = 0
    for x in items[:3]:
        # cap = os.path.splitext(os.path.basename(x.path))[0]
        # cc.append(x) # x.zoom()
        if (ai == direction):
            ai += 1
        cc[ai] = x
        ai += 1;

    rest = items[3:]
    if rest:
        # recurse
        # cc.append(recursiverender(rest, basename, tilewidth, tileheight, z+1))
        cc[direction] = recursiverender(rest, basename, tilewidth, tileheight, direction, z+1)

    newim = fourup([x.get("image") for x in node['children'] if x != None and x.get("image")], tilewidth, tileheight)
    # simplified name works just because there's only one generated tile per level
    newimpath = u"{0}.z{1}.png".format(basename, z)
    newim.save(newimpath)
    node['image'] = newimpath

    return node

def layoutxyz (n, x=0, y=0, z=0, outnode={}):
    # print ("layout", n, x, y, z, file=sys.stderr)
    outnode["{0},{1},{2}".format(x,y,z)] = {
        "text": n['text'],
        "image": n['image']
    }
    if 'children' in n:
        for child, cx, cy in cell_layout(n['children']):
            layout(child, (x*2)+cx, (y*2)+cy, z+1, outnode)
    return outnode

def html (node, title):
    page = u"""<!DOCTYPE html>
<html>
<head>
    <title>""" + title + u"""</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <script src="/lib/leaflet-1.0.1/leaflet.js"></script>
    <link href="/lib/leaflet-1.0.1/leaflet.css" rel="stylesheet" type="text/css">
    <link href="map.css" rel="stylesheet" type="text/css">
</head>
<body>
    <div id="frame" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px">
        <div id="map" style="width: 100%; height: 100%; background: black"></div>
        <div id="text" style="position: absolute; left: 50px; top: 10px; width: auto; color: white">
        </div>
    </div>
<script>
(function() {
  // warning CHANGES TO THIS CODE NEED TO BE ROLLED BACK INTO leaflet.py
  var cell_layout, expandzoom, fourup, layoutxyz, render, split4, tiler, tiles_wrapper, zoom;

  window.tiler = tiler = {};

  tiler.tiles_wrapper = tiles_wrapper = function(path, ext) {
    if (ext == null) { ext = "jpg"; }
    var ret = {};
    ret.get_tile_path = function(z, y, x) {
      return path + ("/z"+z+"y"+y+"x"+x+"."+ext);
    };
    return ret;
  };

  tiler.zoom = zoom = function(tiles, caption, url, x, y, z, maxzoom) {
    var c, i, k, kids, len, len1, node, r, ref, ref1;
    if (x == null) {
      x = 0;
    }
    if (y == null) {
      y = 0;
    }
    if (z == null) {
      z = 0;
    }
    if (maxzoom == null) {
      maxzoom = 3;
    }
    node = {};
    if (caption && x === 0 && y === 0) {
      node['text'] = caption;
    }
    var lastc = Math.pow(2, z) - 1;
    if (url && x === 0 && y === lastc) {
      node['url'] = url
    }
    node['image'] = tiles.get_tile_path(z, y, x);
    if (z < maxzoom) {
      kids = [];
      ref = [0, 1];
      for (i = 0, len = ref.length; i < len; i++) {
        r = ref[i];
        ref1 = [0, 1];
        for (k = 0, len1 = ref1.length; k < len1; k++) {
          c = ref1[k];
          kids.push(zoom(tiles, caption, url, (x * 2) + c, (y * 2) + r, z + 1, maxzoom));
        }
      }
      node['children'] = kids;
    }
    return node;
  };

  split4 = function(items) {
    var c, el, i, l, len, p, ref, results, x;
    l = items.length;
    p = Math.ceil(Math.log(l) / Math.log(4));
    c = Math.max(1, Math.pow(4, p) / 4);
    el = function(x, c) {
      while (x.length < c) {
        x.push(null);
      }
      return x;
    };
    ref = [items.slice(0, c), items.slice(c, c * 2), items.slice(c * 2, c * 3), items.slice(c * 3)];
    results = [];
    for (i = 0, len = ref.length; i < len; i++) {
      x = ref[i];
      results.push(el(x, c));
    }
    return results;
  };

  cell_layout = function(items) {
    return [
      {
        y: 0,
        x: 0,
        item: items[0]
      }, {
        y: 0,
        x: 1,
        item: items[1]
      }, {
        y: 1,
        x: 0,
        item: items[2]
      }, {
        y: 1,
        x: 1,
        item: items[3]
      }
    ];
  };

  tiler.render = render = function(items, tilewidth, tileheight, z, y, x) {
    var g, i, j, kids, len, node, ref;
    if (tilewidth == null) {
      tilewidth = 256;
    }
    if (tileheight == null) {
      tileheight = 256;
    }
    if (z == null) {
      z = 0;
    }
    if (y == null) {
      y = 0;
    }
    if (x == null) {
      x = 0;
    }
    if (items.length === 1) {
      x = items[0];
      if (x === null) {
        return null;
      }
      return zoom(x, '');
    } else {
      node = {};
      node['text'] = '';
      kids = [];
      ref = cell_layout(split4(items));
      for (i = 0, len = ref.length; i < len; i++) {
        g = ref[i];
        kids.push(render(g.item, tilewidth, tileheight, z + 1, (y * 2) + g.y, (x * 2) + g.x));
      }
      node.children = (function() {
        var k, len1, results;
        results = [];
        for (k = 0, len1 = kids.length; k < len1; k++) {
          j = kids[k];
          if (j !== null) {
            results.push(j);
          }
        }
        return results;
      })();
      node.image = fourup((function() {
        var k, len1, ref1, results;
        ref1 = node.children;
        results = [];
        for (k = 0, len1 = ref1.length; k < len1; k++) {
          j = ref1[k];
          if (j !== null) {
            results.push(j.image);
          }
        }
        return results;
      })(), tilewidth, tileheight);
      return node;
    }
  };

  tiler.layoutxyz = layoutxyz = function(n, x, y, z, outnode) {
    var g, i, len, ref;
    if (x == null) {
      x = 0;
    }
    if (y == null) {
      y = 0;
    }
    if (z == null) {
      z = 0;
    }
    if (outnode == null) {
      outnode = {};
    }
    outnode[x + "," + y + "," + z] = n;
    if (n.children) {
      ref = cell_layout(n.children);
      for (i = 0, len = ref.length; i < len; i++) {
        g = ref[i];
        if (g.item) {
          layoutxyz(g.item, (x * 2) + g.x, (y * 2) + g.y, z + 1, outnode);
        }
      }
    }
    return outnode;
  };

  tiler.fourup = fourup = function(images, tilewidth, tileheight) {
    if (tilewidth == null) {
      tilewidth = 256;
    }
    if (tileheight == null) {
      tileheight = 256;
    }
    return function(done) {
      var i, img, imgelts, len, loadcount, results, src, x;
      loadcount = 0;
      images = (function() {
        var i, len, results;
        results = [];
        for (i = 0, len = images.length; i < len; i++) {
          x = images[i];
          if (x !== null) {
            results.push(x);
          }
        }
        return results;
      })();
      imgelts = [];
      results = [];
      for (i = 0, len = images.length; i < len; i++) {
        src = images[i];
        img = new Image;
        imgelts.push(img);
        img.addEventListener("load", function() {
          var canvas, ctx, g, hh, hw, k, len1, ref;
          if (++loadcount >= images.length) {
            canvas = document.createElement("canvas");
            canvas.width = tilewidth;
            canvas.height = tileheight;
            ctx = canvas.getContext("2d");
            hw = tilewidth / 2;
            hh = tileheight / 2;
            ref = cell_layout(imgelts);
            for (k = 0, len1 = ref.length; k < len1; k++) {
              g = ref[k];
              if (g.item) {
                ctx.drawImage(g.item, g.x * hw, g.y * hh, hw, hh);
              }
            }
            return done(null, canvas.toDataURL());
          }
        }, false);
        if (typeof src === "function") {
          console.log("inside 4up, deferring");
          results.push(src(function(err, data) {
            console.log("  inside 4up, GOT DATA");
            return img.src = data;
          }));
        } else {
          results.push(img.src = src);
        }
      }
      return results;
    };
  };

  tiler.expandzoom = expandzoom = function(node) {
    var c, ret, tilespath;
    if (node.zoomable) {
      tilespath = node.image.replace(/\/[^\/]+$/, "");
      var ext = node.image.match(/\.([^\.]+)$/);
      if (ext != null) { ext = ext[1] };
      ret = zoom(tiles_wrapper(tilespath, ext), node.text, node.url);
      return ret;
    }
    if (node.children) {
      node.children = (function() {
        var i, len, ref, results;
        ref = node.children;
        results = [];
        for (i = 0, len = ref.length; i < len; i++) {
          c = ref[i];
          if (c != null) {
              results.push(expandzoom(c));
          }
        }
        return results;
      })();
    }
    return node;
  };

  /* DynamicTiles */
  /*
  A simple GridLayer extension that takes an external "nodes" object as option,
  Nodes are keyed [x,y,z]
  and expected to be of the form: 
      {
          text: "My text",
          image" "imagepath.jpg"
      }
  */
  L.GridLayer.DynamicTiles = L.GridLayer.extend({
      createTile: function (coords, done) { // done = (err, tile)
        // console.log("createTile", coords, this.options, this.options.nodes);
          var tile = document.createElement('div'),
            node = this.options.nodes[coords.x+","+coords.y+","+coords.z],
            defer = false;

          tile.classList.add("tile");
          if (node != undefined) {
            // console.log("NODE", node);
            if (node.image) {
              var img = document.createElement("img");
              defer = true;
              img.addEventListener("load", function () {
                done(null, tile);
              })
              img.src = node.image;
              tile.appendChild(img);
              img.classList.add("imagetile");
            }
            if (node.text) {
                  //console.log("text", node.text);
                var textdiv = document.createElement("div");
                textdiv.innerHTML = node.text;
                tile.appendChild(textdiv);
                textdiv.classList.add("text");
            }
            // if (node.url) {
            //   console.log("NODE HAS URL!", node.url);
            //     var urldiv = document.createElement("div"),
            //       urllink = document.createElement("a"),
            //       m = node.url.search(/\/([^\/]+)$/);
            //     urllink.innerHTML = (m != null) ? m[1] : "LINK";
            //     urldiv.appendChild(urllink);
            //     urldiv.classList.add("url");
            //     tile.appendChild(urldiv);
            // }
            if (node.background) {
              tile.style.color = node.background;
            }
            if (node.class) {
              tile.classList.add(node.class);
            }
            tile.classList.add("z"+coords.z);
          } else {
            tile.innerHTML = [coords.x, coords.y, coords.z].join(', ');
            tile.classList.add("coords");
          }
          // tile.style.outline = '1px solid red';
          if (!defer) {
            window.setTimeout(function () {
              done(null, tile);
            }, 250);
          }
          return tile;
      }
  });""

  L.gridLayer.dynamicTiles = function(opts) {
      return new L.GridLayer.DynamicTiles(opts);
  };

}).call(this);

(function () {


function getjson (url, callback) {
    var request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.onload = function() {
      if (request.readyState == XMLHttpRequest.DONE && request.status >= 200 && request.status < 400) {
        callback(null, JSON.parse(request.responseText));
      } else {
        callback("server error");
      }
    };
    request.onerror = function() {
        callback("connection error");
    };
    request.send();
}

var map = L.map('map', {
        editable: true,
        maxZoom: 100,
        minZoom: 0,
        zoom: 0,
        crs: L.CRS.Simple,
        center: new L.LatLng(0,0),
    });
var data = """ + json.dumps(node) + """;

var nodes = (tiler.layoutxyz(tiler.expandzoom(data)));
map.addLayer( L.gridLayer.dynamicTiles({
    minZoom: 0,
    nodes: nodes
}) );

var yx = L.latLng,
    xy = function(x, y) {
        if (L.Util.isArray(x)) {    // When doing xy([x, y]);
            return yx(x[1], x[0]);
        }
        return yx(y, x);  // When doing xy(x, y);
    };
// map.setView(xy(0.5 * 256, -0.5 * 256), 0);

})();
</script>
</body>
</html>
"""
    return page

def make_gallery(args):
    """
    to do -- separate the actual tiling process...
    make tiling a separate pass ON THE ACTUAL NODE jSON

    NB: this command accepts two different kinds of input.
    1. One or more images as (argv) arguments -or-
    2. A JSON stream (one object per line) on stdin.
    """

    bgcolor = None # (0, 0, 0)

    items = []
    if args.input:
        for x in args.input:
            i = {'url': x}
            items.append(i)
    else:
        for line in sys.stdin:
            line = line.rstrip()
            if line and not line.startswith("#"):
                try:
                    item = json.loads(line)
                    items.append(item)
                except ValueError as e:
                    print (u"Error reading line {0}, {1}".format(line, e).encode("utf-8"), file=sys.stderr)
    # Ensure / Generate tiles per image
    # items.sort(key=lambda x: x['url'])
    tiles = []
    for item in items:
        n = item['url']
        # print (n, file=sys.stderr)
        path = os.path.join(args.tilespath, n)
        # TODO date format...
        caption = ''
        if 'text' or 'date' in item:
            caption += u'<p class="caption">';
        if 'text' in item:
            caption += u'<span class="text">{0}</span>'.format(autolink(item['text']))
        if 'date' in item:
            dt = parse8601(item['date'], "%d %b %Y")
            caption += u'<span class="date">{0}</span>'.format(dt)
        
        if 'src' in item:
            caption += u'<a class="url" href="{0}">{1}</a>'.format(item['src'], "SRC")
        elif 'url' in item:
            # ext = os.path.splitext(urlparse.urlparse(item['url']).path)[1]
            # if ext:
            #     ext = ext[1:].upper()
            caption += u'<a class="url" href="{0}">{1}</a>'.format(item['url'], "SRC")
        
        if 'text' or 'date' in item:
            caption += u'</p>';

        t = tiles_wrapper(path, item['url'], text=caption)
        tiles.append(t)
        tile0 = t.get_tile_path(0, 0, 0) # os.path.join(path, args.tilename.format({'x': 0, 'y': 0, 'z': 0}))
        if not os.path.exists(tile0) or args.force:
            print ("Tiling {0}".format(n), file=sys.stderr)
            try:
                im = Image.open(n)
                try:
                    os.makedirs(path)
                except OSError:
                    pass
                tile_image(im, args.zoom, args.tilewidth, args.tileheight, path+"/", args.tilename, bgcolor)
                # tiles.append(t)

            except IOError as e:
                print ("Missing {0}, skipping".format(n), file=sys.stderr)
                tiles = tiles[:-1]

    # DO THE LAYOUT, generating intermediate tiles (zoom outs)
    if args.reverse:
        tiles.reverse()
    tiles = [t.zoom() for t in tiles]
    basename = os.path.join(args.tilespath, args.name)
    if args.recursive:
        root_node = recursiverender(tiles, basename, args.tilewidth, args.tileheight, args.direction)
    else:
        root_node = gridrender(tiles, basename, args.tilewidth, args.tileheight)

    # OUTPUT ROOT NODE
    if args.html:
        print (html(root_node, args.name))
    else:
        print (json.dumps(root_node, indent=args.indent))


if __name__ == "__main__":

    ap = ArgumentParser("")

    ap.add_argument("--basepath", default=".")
    ap.add_argument("--baseuri", default="")

    ap.add_argument("--tilespath", default="tiles")

    ap.add_argument("--tilewidth", type=int, default=256)
    ap.add_argument("--tileheight", type=int, default=256)
    ap.add_argument("--zoom", type=int, default=3)

    ap.add_argument("--tilename", default="z{0[z]}y{0[y]}x{0[x]}.png")
    ap.add_argument("--reverse", default=False, action="store_true")
    ap.add_argument("--indent", default=2, type=int)
    ap.add_argument("--recursive", default=False, action="store_true")

    ap.add_argument("--force", default=False, action="store_true")

    subparsers = ap.add_subparsers(help='sub-command help')
    ap_gallery = subparsers.add_parser('gallery', help='Create a grid gallery of images')
    ap_gallery.add_argument("input", nargs="*")
    ap_gallery.add_argument("--html", default=False, action="store_true")
    ap_gallery.add_argument("--recursive", default=False, action="store_true")
    ap_gallery.add_argument("--reverse", default=False, action="store_true")
    ap_gallery.add_argument("--direction", type=int, default=3, help="cell to recursively expand into, 0-3, default: 3 (bottom-right)")
    ap_gallery.add_argument("--name", default="gallery")
    ap_gallery.set_defaults(func=make_gallery)

    args = ap.parse_args()
    args.func(args)
