/**
 * Copyright 2010 The ForPlay Authors
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package forplay.html;

import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.ImageElement;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Unit;

import forplay.core.Asserts;
import forplay.core.Image;
import forplay.core.ImageLayer;
import forplay.core.ResourceCallback;

class HtmlImageLayerDom extends HtmlLayerDom implements ImageLayer {

  private float width, height;
  private boolean widthSet, heightSet;
  private float sx, sy, sw, sh;
  private boolean sourceRectSet;
  private boolean repeatX, repeatY;

  private HtmlImage htmlImage;

  public HtmlImageLayerDom() {
    super(Document.get().createDivElement());

    setRepeatX(false);
    setRepeatY(false);
  }

  HtmlImageLayerDom(final Image img) {
    this();
    setImage(img);
  }

  @Override
  public void clearHeight() {
    heightSet = true;
    applySize();
  }

  @Override
  public void clearSourceRect() {
    this.sourceRectSet = false;
    applyBackgroundSize();
  }

  @Override
  public void clearWidth() {
    widthSet = true;
    applySize();
  }

  @Override
  public Image image() {
    return htmlImage;
  }

  @Override
  public void setHeight(float height) {
    Asserts.checkArgument(height > 0, "Height must be > 0");

    heightSet = true;
    this.height = height;
    applySize();
  }

  @Override
  public void setImage(final Image img) {
    Asserts.checkArgument(img instanceof HtmlImage);

    // Make sure redundant setImage() calls don't cost much.
    if (htmlImage == img) {
      return;
    }

    htmlImage = (HtmlImage) img;
    ImageElement imgElem = htmlImage.img.cast();
    element().getStyle().setBackgroundImage("url(" + imgElem.getSrc() + ")");
    element().getStyle().setOverflow(Overflow.HIDDEN);

    img.addCallback(new ResourceCallback<Image>() {
      @Override
      public void done(Image resource) {
        applySize();
        applyBackgroundSize();
      }

      @Override
      public void error(Throwable err) {
        // Nothing to be done about errors.
      }
    });
  }

  @Override
  public void setRepeatX(boolean repeat) {
    Asserts.checkArgument(!repeat || !sourceRectSet, "Cannot repeat when source rect is used");

    repeatX = repeat;
    applyBackgroundSize();
  }

  @Override
  public void setRepeatY(boolean repeat) {
    Asserts.checkArgument(!repeat || !sourceRectSet, "Cannot repeat when source rect is used");

    repeatY = repeat;
    applyBackgroundSize();
  }

  @Override
  public void setSourceRect(float sx, float sy, float sw, float sh) {
    Asserts.checkState(!repeatX && !repeatY, "Cannot use source rect when repeating x or y");
    Asserts.checkArgument(sw != 0 && sh != 0); // Will cause div-by-zero

    // Early out if there's no change. applyBackgroundSize() isn't free.
    if (sourceRectSet &&
        (this.sx == sx) && (this.sy == sy) &&
        (this.sw == sw) && (this.sh == sh)) {
      return;
    }

    this.sourceRectSet = true;
    this.sx = sx;
    this.sy = sy;
    this.sw = sw;
    this.sh = sh;
    applyBackgroundSize();
  }

  @Override
  public void setWidth(float width) {
    Asserts.checkArgument(width > 0, "Width must be > 0");

    widthSet = true;
    this.width = width;
    applySize();
  }

  @Override
  public void setSize(float width, float height) {
    Asserts.checkArgument(width > 0 && height > 0,
                          "Width and height must be > 0 (got %dx%d)", width, height);

    widthSet = true;
    this.width = width;
    heightSet = true;
    this.height = height;
    applySize();
  }

  private void applyBackgroundSize() {
    Style style = element().getStyle();

    // Set background-repeat to get the right repeating behavior.
    String repeat = repeatX ? "repeat-x " : "";
    repeat += repeatY ? "repeat-y" : "";
    style.setProperty("backgroundRepeat", repeat);

    // Set background-size to get the right pinning behavior.
    if (sourceRectSet) {
      float wratio = widthSet ? (width / sw) : (image().width() / sw);
      float hratio = heightSet ? (height / sh) : (image().height() / sh);
      if (wratio == 0) {
        wratio = 1;
      }
      if (hratio == 0) {
        hratio = 1;
      }
      float backWidth = image().width() * wratio;
      float backHeight = image().height() * hratio;

      style.setProperty("backgroundSize", backWidth + "px " + backHeight + "px");
      style.setProperty("backgroundPosition", (-sx * wratio) + "px " + (-sy * hratio) + "px");
    } else {
      String size = repeatX ? image().width() + "px " : "100% ";
      size += repeatY ? image().height() + "px" : "100%";
      style.setProperty("backgroundSize", size);
      style.clearProperty("backgroundPosition");
    }
  }

  private void applySize() {
    Style style = element().getStyle();
    style.setWidth(widthSet ? width : htmlImage.img.getWidth(), Unit.PX);
    style.setHeight(heightSet ? height : htmlImage.img.getHeight(), Unit.PX);
    if (sourceRectSet) {
      applyBackgroundSize();
    }
  }

  @Override
  public float width() {
    Asserts.checkNotNull(htmlImage, "Image must not be null");
    if (widthSet) {
      return width;
    } else {
      return htmlImage.width();
    }
  }

  @Override
  public float height() {
    Asserts.checkNotNull(htmlImage, "Image must not be null");
    if (heightSet) {
      return height;
    } else {
      return htmlImage.height();
    }
  }

  @Override
  public float scaledWidth() {
    return transform().scaleX() * width();
  }

  @Override
  public float scaledHeight() {
    return transform().scaleY() * height();
  }
}