﻿function equalsImage(left, right) {
  if (left === right) return true;
  if (typeof(left) === tUNDEFINED || typeof(right) === tUNDEFINED) return false;
  if (!WAVE.isObject(left)) left = { url: left };
  if (!WAVE.isObject(right)) right = { url: right };
  if (left.url === right.url) {
    if (WAVE.has(left, 'clip')) {
      if (WAVE.has(right, 'clip')) {
        return left.left|0 === right.left|0
          && left.right|0  === right.right|0
          && left.bottom|0 === right.bottom|0
          && left.top|0    === right.top|0;
      }
    } else {
      if (!WAVE.has(right, 'clip')) return true;
    }
  }
  return false;
}

function loadImage(image, onload, onerror, urlMap) {
  if (!WAVE.isObject(image)) image = { url: image };
  image.url = WAVE.strDefault(ensureUrl(WAVE.strDefault(image.url)));
  var img = new Image();
  img.onerror = function () {
    image.error = true;
    if (typeof(onerror) !== tUNDEFINED)
      onerror.apply(img, arguments);
  };
  if (WAVE.has(image, 'clip')) {
    var clip = image.clip;
    var left =   clip.left|0;
    var right  = clip.right|0;
    var bottom = clip.bottom|0;
    var top    = clip.top|0;
    var
      clipWidth = right - left,
      clipHeight = bottom - top;
    var canvas = document.createElement('canvas');
    canvas.width = clipWidth;
    canvas.height = clipHeight;
    img.onload = function () {
      image.load = true;
      image.width = clipWidth;
      image.height = clipHeight;
      var ctx = canvas.getContext('2d');
      ctx.drawImage(this, left, top, clipWidth, clipHeight, 0, 0, clipWidth, clipHeight);
      if (typeof(onload) !== tUNDEFINED)
        onload.apply(canvas, arguments);
      delete image.img;
    };
    image.element = canvas;
  } else {
    img.onload = function () {
      image.load = true;
      image.width = this.width;
      image.height = this.height;
      var that = this;
      if (typeof(onload) !== tUNDEFINED)
        onload.apply(img, arguments);
    };
    image.element = img;
  }
  img.src = image.url;
  image.free = free;

  return image;

  function free() {
    if (image.element.parentNode)
      image.element.parentNode.removeChild(image.element);
    delete image.element;
    delete image.error;
    delete image.load;
    delete image.img;
    delete image.width;
    delete image.height;
    delete image.free;
  }
  function ensureUrl(url) { return url.indexOf('$') === 0 && WAVE.isObject(urlMap) ? urlMap[url.substr(1)] : url; }
}

function makeDraggable(element, ondrag, onmove, ondrop, preventDefault) {
  element.addEventListener('mousedown', mousedown, false);
  element.addEventListener('touchstart', touchstart, false);

  function mousedown(event) {
    if (event.button === 0) {
      if (preventDefault) event.preventDefault();
      dragstart(event.pageX, event.pageY);
    }
  }
  function touchstart(event) {
    var touches = event.touches;
    if (touches.length === 1) {
      if (preventDefault) event.preventDefault();
      dragstart(touches[0].pageX, touches[0].pageY);
    }
  }
  function dragstart(x, y) {
    if (typeof(ondrag) !== tUNDEFINED)
      ondrag.call(element, x, y);
    document.addEventListener('mousemove', mousemove, false);
    document.addEventListener('touchmove', touchmove, false);
    document.addEventListener('mouseup', mouseup, false);
    document.addEventListener('touchend', touchend, false);
    function mousemove(event) {
      event.preventDefault();
      var
        pageX = event.pageX,
        pageY = event.pageY;
      dragmove(x, y, x - pageX, y - pageY);
      x = pageX; y = pageY;
    }
    function touchmove(event) {
      event.preventDefault();
      var touches = event.touches;
      var
        pageX = touches[0].pageX,
        pageY = touches[0].pageY;
      dragmove(x, y, x - pageX, y - pageY);
      x = pageX; y = pageY;
    }
    function mouseup(event) {
      dragend(event.pageX, event.pageY);
      cleanup();
    }
    function touchend(event) {
      dragend(x, y);
      cleanup();
    }
    function cleanup() {
      document.removeEventListener('mousemove', mousemove, false);
      document.removeEventListener('touchmove', touchmove, false);
      document.removeEventListener('mouseup',   mouseup,   false);
      document.removeEventListener('touchend',  touchend,  false);
    }
  }
  function dragmove(x, y, offsetX, offsetY) {
    if (typeof(onmove) !== tUNDEFINED)
      onmove.call(element, x, y);
    element.scrollLeft += offsetX;
    element.scrollTop += offsetY;
  }
  function dragend(x, y) {
    if (typeof(ondrop) !== tUNDEFINED)
      ondrop.call(element, x, y);
  }
}

var fImageViewIdSeed = 0;
published.ImageView = function (init, urlMap) {
  if (typeof(init) === tUNDEFINED || init === null) init = {};
  if (WAVE.isString(init)) init = { DIV: init };
  if (!WAVE.has(init, 'DIV')) throw "ImageView.ctor(init.thumbnails)";

  var fId = published.CLS_IMAGE_VIEW + fImageViewIdSeed++;

  var imageView = this;
  WAVE.extend(imageView, WAVE.EventManager);

  var fViewbox = build(WAVE.isString(init.DIV) ? WAVE.id(init.DIV) : init.DIV);

  var fImage;
  imageView.image = function (image) {
    if (typeof(image) !== tUNDEFINED)  {
      fImage = updateImage(image);
    }
    return fImage;
  };
  imageView.clear = function (flag) {
    if (flag)
      fViewbox.innerHTML = '';
    return fViewbox.innerHTML === '';
  };

  imageView.image(init.image);

  window.addEventListener('resize', function() { update(imageView.image() || {}); }, false);

  return imageView;

  function build(parent) {
    var viewbox = document.createElement('div');
    viewbox.className = published.CLS_IMAGE_VIEW;
    parent.appendChild(viewbox);
    makeDraggable(viewbox, function (x, y) {
      var
        image = imageView.image(),
        offset = position(viewbox),
        box = size(viewbox),
        client = { x: x - offset.x - box.x / 2, y: y - offset.y - box.y / 2 },
        sz = size(fImage.element),
        newClient = {
          x: client.x * fImage.width / sz.x,
          y: client.y * fImage.height / sz.y
        };
      if (fImage.width > sz.x || fImage.height > sz.y) {
        viewbox.className += ' ' + published.CLS_IMAGE_VIEW_ZOOM;
        var scr = scroll(viewbox);
        viewbox.scrollLeft = newClient.x + scr.x / 2 - (x - offset.x);
        viewbox.scrollTop = newClient.y + scr.y / 2 - (y - offset.y);
      }
    }, undefined, update, true);
    return viewbox;
  }

  function updateImage(image) {
    if (typeof(image) === tUNDEFINED) return;
    imageView.clear(true);
    if (image === null) return;

    if (WAVE.isObject(image) && WAVE.has(image, 'element')) {
      if (!image.error) fViewbox.appendChild(image.element);
      update(image);
    } else {
      image = loadImage(image, function () {
        this.className = published.CLS_IMAGE_VIEW_IMAGE;
        if (imageView.clear())
          fViewbox.appendChild(this);
        update(image);
      }, function () {
        update(image);
      }, urlMap);
    }
    return image;
  }

  function update(image) {
    if (typeof(fViewbox) === tUNDEFINED) return;
    fViewbox.className = published.CLS_IMAGE_VIEW;
    if (image.error) fViewbox.className += ' ' + published.CLS_IMAGE_BOX_ERROR;
    else if (!image.load) fViewbox.className += ' ' + published.CLS_IMAGE_BOX_LOADING;
    else {
      var
        sz = size(fViewbox), aspect = sz.x / sz.y,
        imageAspect = (image.width|0) / (image.height|0);
      if (!isNaN(imageAspect))
        fViewbox.className += ' ' + ((imageAspect >= aspect) ? published.CLS_IMAGE_BOX_WIDER : published.CLS_IMAGE_BOX_HIGHER);
    }
  }

  function position(elm) {
    var ox=0, oy=0;
    while (elm !== null)
      ox += elm.offsetLeft, oy += elm.offsetTop, elm = elm.offsetParent;
    return { x: ox, y: oy };
  }
  function size(elm) { return { x: elm.offsetWidth, y: elm.offsetHeight }; }
  function scroll(elm) { return { x: elm.scrollWidth, y: elm.scrollHeight }; }
};//ImageView

/*
Image = {
  url: $URL_NAME | URL,
  clip: { top: NUMBER, bottom: NUMBER, left: NUMBER, right: NUMBER }
};
Gallery = {
  thumbnails: DIV,
  preview: DIV,
  urls: {
    name: URL
  },
  images: [
    {
      id: TEXT,
      image: Image | $URL_NAME | URL,
      thumb: Image | URL,
      title: TEXT,
      content: TEXT | HTML,
      isText: BOOL
    } | Image | URL
  ]
} | DIV_ID;
*/
var fGalleryIdSeed = 0;
published.Gallery = function (init) {
  if (typeof(init) === tUNDEFINED || init === null) init = {};
  if (WAVE.isString(init)) init = { thumbnails: init };
  if (!WAVE.has(init, 'thumbnails')) throw "Gallery.ctor(init.thumbnails)";
  var hasUrls = WAVE.has(init, 'urls');
  if (hasUrls && !WAVE.isObject(init.urls)) throw "Gallery.ctor(init.urls)";

  var fId = fGalleryIdSeed++;

  var gallery = this;
  WAVE.extend(gallery, WAVE.EventManager);

  gallery.urls = (hasUrls ? init.urls : {});

  var fUpdate;
  var fThumbnails = build(WAVE.isString(init.thumbnails) ? WAVE.id(init.thumbnails) : init.thumbnails);
  var fPreview = WAVE.isString(init.preview) ? new published.ImageView(init.preview, gallery.urls) : init.preview;

  var fCurrent;
  var fItems = [];
  var fItemsLookup = {};

  var fItemIdSeed = 0;
  gallery.Item = function (init, gallery, view) {
    var seqId = fItemIdSeed++;

    var fId = WAVE.isObject(init) && WAVE.has(init, 'id') ? init.id : seqId;
    init.id = fId;
    var fInit = {};

    var item = this;
    WAVE.extend(item, WAVE.EventManager);

    var fGallery = gallery,
        fView = view,
        fImage,
        fThumbImage;

    var fElem = document.createElement('li'),
        fThumb = document.createElement('div'),
        fContent = document.createElement('div');

    fElem.className = published.CLS_GALLERY_ITEM;
    fContent.className = published.CLS_GALLERY_CONTENT;
    fThumb.className = published.CLS_GALLERY_THUMBNAIL;
    fElem.appendChild(fThumb);
    fElem.appendChild(fContent);
    fElem.onclick = function() {
      gallery.current(fId);
    };

    item.id = function () { return fId; };

    item.title = function (title) {
      if (typeof(title) !== tUNDEFINED
        && fInit.title !== title)
        updateTitle(fInit.title = title);
      return WAVE.strDefault(fInit.title, fId);
    };

    item.content = function (content, isText) {
      if (typeof(content) !== tUNDEFINED
        && (fInit.content !== content
          || !!fInit.isText !== !!isText))
        updateContent(fInit.content = content, fInit.isText = !!isText);
      return WAVE.strDefault(fInit.content);
    };

    item.isText = function () { return !!fInit.isText; };

    item.image = function (image) {
      if (typeof(image) !== tUNDEFINED
        && !equalsImage(fInit.image, image))
        updateImage(fInit.image = image);
      return fImage;
    };

    item.thumb = function (image) {
      if (typeof(image) !== tUNDEFINED
        && !equalsImage(fInit.thumb, image))
        updateThumbnail(fInit.thumb = image);
      return fThumbImage;
    };

    item.data = function (data) {
      if (typeof(data) !== tUNDEFINED) {
        fInit.data = data;
      }
      return fInit.data;
    };

    item.show = function () {
      fImage = fView.image(fImage);
    };

    item.update = update;

    item.free = function () {
      if (fElem.parentNode)
        fElem.parentNode.removeChild(fElem);
      if (fView.image() === fImage && fImage.element.parentNode)
        fImage.element.parentNode.removeChild(fImage.element);
    };

    item.element  = function () {
      return fElem;
    };

    item.update(init);

    return item;

    function update(init) {
      if (!WAVE.isObject(init) || WAVE.has(init, "url"))  init = { image: init };
      if (!WAVE.has(init, 'image')) throw "Gallery.Item.update(init.image)";
      if (init.id !== fId) throw "Gallery.Item.update(init.id)";
      item.image(init.image);
      item.thumb(init.thumb || init.image);
      item.title(init.title);
      item.content(init.content, init.isText);
      item.data(init.data);
    }

    function updateImage(image) {
      var selected = false;
      if (typeof(fImage) !== tUNDEFINED) {
        selected = fImage === view.image();
        if (WAVE.has(fImage, 'free')) fImage.free();
      }
      fImage = image;
      if (selected) item.show();
    }

    function updateThumbnail(image) {
      fThumb.innerHTML = '';
      fThumb.className = published.CLS_GALLERY_THUMBNAIL + ' ' + published.CLS_IMAGE_BOX_LOADING;
      fThumbImage = loadImage(image, function() {
        this.className = published.CLS_GALLERY_IMAGE;
        this.id = published.CLS_GALLERY_IMAGE + item.id();
        fThumb.appendChild(this);
        fThumb.className = published.CLS_GALLERY_THUMBNAIL + ' '
          + ((this.width >= this.height) ? published.CLS_IMAGE_BOX_WIDER : published.CLS_IMAGE_BOX_HIGHER);
      }, function () {
        fThumb.className = published.CLS_GALLERY_THUMBNAIL + ' ' + published.CLS_IMAGE_BOX_ERROR;
      }, gallery.urls);
      fUpdate();
    }

    function updateContent(content, isText) {
      if (isText) fContent.innerText = content;
      else fContent.innerHTML = content;
    }

    function updateTitle(title) { fThumb.title = title; }
  };

  var fUpdateCurrentWithEvent = withEvent(gallery, published.EVT_GALLERY_CHANGE, updateCurrent);

  gallery.current = function (id) {
    if (typeof(id) !== tUNDEFINED) {
      var current = fUpdateCurrentWithEvent(id);
      if (typeof(current) !== tUNDEFINED) fCurrent = current;
    }
    if (typeof(fCurrent) !== tUNDEFINED) return fCurrent.id();
  };
  gallery.has = has;
  gallery.get = get;
  var fRemoveWithEvent = withEvent(gallery, published.EVT_GALLERY_REMOVE, remove);
  gallery.remove = function (id) {
    if (!has(id)) return true;
    return !!fRemoveWithEvent(id);
  };
  gallery.images = function () { return fItems; };
  gallery.update = withEvent(gallery, published.EVT_GALLERY_ADD, updateItem);

  WAVE.each(init.images, updateItem);

  fUpdate();

  return gallery;

  function build(parent) {
    var
      thumbnails = document.createElement('ul'),
      prev = document.createElement('div'),
      next = document.createElement('div');
    thumbnails.className = published.CLS_GALLERY_LIST;
    prev.className = published.CLS_GALLERY_PREV;
    next.className = published.CLS_GALLERY_NEXT;
    prev.onclick = function (e) {
      e.preventDefault();
      var sz = size(thumbnails.firstChild);
      scroll(thumbnails, { x: -sz.x, y: -sz.y}, 400, undefined, update);
    };
    next.onclick = function (e) {
      e.preventDefault();
      var sz = size(thumbnails.firstChild);
      scroll(thumbnails, sz, 400, undefined, update);
    };
    makeDraggable(thumbnails, undefined, update, update);
    parent.appendChild(prev);
    parent.appendChild(thumbnails);
    parent.appendChild(next);
    window.addEventListener('resize', update, false);
    fUpdate = update;
    return thumbnails;
    function update() {
      prev.className = published.CLS_GALLERY_PREV
        + ((fThumbnails.scrollLeft === 0 && fThumbnails.scrollTop === 0) ? ' ' + published.CLS_GALLERY_DISABLED : '');
      next.className = published.CLS_GALLERY_NEXT
        + ((fThumbnails.scrollLeft + fThumbnails.clientWidth === fThumbnails.scrollWidth
          && fThumbnails.scrollTop + fThumbnails.clientHeight === fThumbnails.scrollHeight) ? ' ' + published.CLS_GALLERY_DISABLED : '');
    }
  }

  function updateItem(item) {
    var it;
    if (has(item.id)) {
      it = get(item.id);
      it.update(item);
    } else {
      it = new gallery.Item(item, gallery, fPreview);
      it.order = item.order|0;
      var idx = insertAt(fItems, it, orderComp);
      fThumbnails.insertBefore(it.element(), WAVE.has(fItems, idx) ? fItems[idx].element() : null);
      fItems.splice(idx, 0, it);
      fItemsLookup[it.id()] = it;
    }
    return it;
  }

  function has(id) {
    if (typeof(id) === tUNDEFINED) return false;
    return WAVE.has(fItemsLookup, id);
  }

  function get(id) { return fItemsLookup[id]; }

  function updateCurrent(id) {
    if (!has(id)) return;
    var item = get(id);

    if (typeof(fCurrent) !== tUNDEFINED)
      fCurrent.element().className = published.CLS_GALLERY_ITEM;
    item.element().className = published.CLS_GALLERY_ITEM + ' ' + published.CLS_GALLERY_SELECTED;
    item.show();
    return item;
  }

  function remove(id) {
    if (!has(id)) return true;
    get(id).free();
    fItems = fItems.filter(function (it) { return it.id() !== id; });
    delete fItemsLookup[id];
    return true;
  }

  function insertAt(sorted, val, comp) {
    var low = 0, hig = sorted.length;
    var mid = -1, c = 0;
    while(low < hig) {
      mid = ((low + hig)/2) | 0;
      c = comp(sorted[mid], val);
      if (c < 0) low = mid + 1;
      else if ( c > 0) hig = mid;
      else  {
        while (mid < hig && comp(sorted[mid], val) === 0) mid++;
        return mid;
      }
    }
    return low;
  }

  function orderComp(a, b) { return a.order - b.order; }

  function size(elm) { return { x: elm.offsetWidth, y: elm.offsetHeight }; }
  function animate(opts) {
    var start = Date.now();
    var id = setInterval(function () {
      var progress = (Date.now() - start) / (+opts.duration || 1000);
      if (progress > 1) progress = 1;
      var cont = opts.step((opts.delta || def)(progress)) || true;
      if (progress === 1 || !cont) clearInterval(id);
    }, +opts.delay || 10);
    function def(progress) { return progress; }
  }
  function scroll(elm, to, duration, delta, onstep) {
    var scr = { x: elm.scrollLeft, y: elm.scrollTop };
    animate({
      duration: duration,
      delta: delta,
      step: function (delta) {
        elm.scrollLeft = scr.x + to.x * delta;
        elm.scrollTop = scr.y + to.y * delta;
        if (onstep)
          onstep.call(elm, delta);
    } });
  }

  function withEvent(that, evt, func) {
    return function () {
      var evtArgs = { evt: evt, phase: published.EVT_PHASE_BEFORE, args: arguments, abort: false };
      that.eventInvoke(evt, evtArgs);
      if (evtArgs.abort === true) return;

      var result = func.apply(that, arguments);

      evtArgs.result = result;
      evtArgs.phase = published.EVT_PHASE_AFTER;
      delete evtArgs.abort;
      that.eventInvoke(evt, evtArgs);
      return result;
    };
  }
};//Gallery

published.GallerySimple = function (init) {
  if (typeof (init) === tUNDEFINED || init === null || typeof (init.DIV) === tUNDEFINED || init.DIV === null) throw "GallerySimple.ctor(init.DIV)";
  var fDIV = WAVE.isString(init.DIV) ? WAVE.id(init.DIV) : init.DIV;
  var preview = document.createElement('div');
  preview.className = published.CLS_GALLERY_SIMPLE_PREVIEW;
  init.preview = new published.ImageView({ DIV: preview }, init.urls);
  init.thumbnails = document.createElement('div');
  init.thumbnails.className = published.CLS_GALLERY_SIMPLE_THUMBNAILS;
  fDIV.appendChild(init.thumbnails);
  fDIV.appendChild(preview);
  return published.Gallery.call(this, init);
};//GallerySimple