import { pt, rect } from "lively.graphics";
import { arr, string, obj } from "lively.lang";

const charSizeProperties = ["fontFamily", "fontSize", "fontWeight", "fontStyle", "textDecoration"];

export default class FontMetric {

  static default() {
    if (!this._fontMetric)
      throw new Error("FontMetric has not yet been initialized!")
    return this._fontMetric;
  }

  static initDefault(domEnv) {
    if (!this._fontMetric) {
      if (!domEnv && typeof document === "undefined")
        throw new Error("Cannot initialize FontMetric without document");
      if (!domEnv) domEnv = {document}
      this._fontMetric = this.forDOMEnv(domEnv);
    }
    return this._fontMetric;
  }

  static removeDefault() {
    if (this._fontMetric) {
      this._fontMetric.uninstall();
      this._fontMetric = null;
    }
  }

  static forDOMEnv({document}) {
    var fontMetric = new FontMetric();
    fontMetric.install(document, document.body);
    return fontMetric;
  }

  constructor() {
    this.charMap = {};
    this.element = null;
    this.cachedBoundsInfo = {};
  }

  install(doc, parentEl) {
    this.element = doc.createElement("div");
    this.element.name = "fontMetric";
    this.setMeasureNodeStyles(this.element.style, true);
    parentEl.appendChild(this.element);
  }

  uninstall() {
    if (!this.element) return
    if (this.element.parentNode) this.element.parentNode.removeChild(this.element);
    this.element = null;
  }

  setMeasureNodeStyles(style, isRoot) {
    style.width = style.height = "auto";
    style.left = style.top = "0px";
    style.visibility = "hidden";
    style.position = "absolute";
    style.whiteSpace = "pre";
    style.font = "inherit";
    style.overflow = isRoot ? "hidden" : "visible";
  }

  measure(style, text) {
    var { fontFamily, fontSize, fontWeight, fontStyle, textDecoration } = style,
        rect = null;
    this.element.textContent = text;
    this.element.style.fontFamily = fontFamily;
    this.element.style.fontSize = fontSize + "px";
    this.element.style.fontWeight = fontWeight,
    this.element.style.fontStyle = fontStyle,
    this.element.style.textDecoration = textDecoration;
    try {
      rect = this.element.getBoundingClientRect();
    } catch(e) {
      rect = {width: 0, height:0};
    };
    return {
      height: rect.height,
      width: rect.width
    }
  }

  charBoundsFor(style, str) {
    let nCols = str.length,
        bounds = new Array(nCols),
        { cachedBoundsInfo: { bounds: cachedBounds, str: cachedStr, style: cachedStyle } } = this,
        useCache = cachedBounds && obj.equals(cachedStyle, style),
        fontIsProportional = this.isProportional(style.fontFamily),
        adjustSpacing = fontIsProportional && !style.fixedCharacterSpacing;
    for (let col = 0, x = 0; col < nCols; col++) {
      let width, height, char = str[col];
      if (adjustSpacing) {
        useCache = useCache && char === cachedStr[col];
        if (useCache)
          ({ width, height } = cachedBounds[col]);
        else {
          let prefix = str.substr(0, col+1);
          ({ width, height } = this.measure(style, prefix));
          width -= x;
        }
      } else {
        ({ width, height } = this.sizeFor(style, char));
      }
      bounds[col] = { x, y: 0, width, height };
      x += width;
    }
    if (adjustSpacing) this.cachedBoundsInfo = { bounds, str, style };
    return bounds;
  }

  isProportional(fontFamily) {
    let style = { fontFamily, fontSize: 12 },
        w_width = this.sizeFor(style, 'w').width,
        i_width = this.sizeFor(style, 'i').width;
    return w_width !== i_width;
  }

  sizeFor(style, char) {
    if (char.length > 1) return this.measure(style, char);

    // Select style properties relevant to individual character size
    style = obj.select(style, charSizeProperties);

    if (!this.charMap[style]) {
      this.charMap[style] = {};
    }
    if (!this.charMap[style][char])
      this.charMap[style][char] = this.measure(style, char);

    return this.charMap[style][char];
  }

  asciiSizes(style) {
    var result = {};
    for (var i = 32; i <= 126; i++) {
      var char = String.fromCharCode(i);
      result[char] = this.sizeFor(style, char)
    }
    return result;
  }

  defaultLineHeight(style) {
    return this.sizeFor(style, " ").height;
  }

}

@@ -90,108 +90,8 @@
 %22;%0A%0A
-const charSizeProperties = %5B%22fontFamily%22, %22fontSize%22, %22fontWeight%22, %22fontStyle%22, %22textDecoration%22%5D;%0A
 %0Aexp
@@ -3544,68 +3544,8 @@
 ) %7B%0A
-    if (char.length %3E 1) return this.measure(style, char);%0A%0A
     
@@ -3617,52 +3617,368 @@
     
-style = obj.select(style, charSizeProperties
+let %7B fontFamily, fontSize,%0A          fontWeight, fontStyle, textDecoration %7D = style,%0A        relevantStyle = %7B fontFamily, fontSize,%0A                          fontWeight, fontStyle, textDecoration %7D;%0A%0A    if (char.length %3E 1) return this.measure(relevantStyle, char);%0A%0A    let styleKey = %5BfontFamily, fontSize, fontWeight, fontStyle, textDecoration%5D.join('-'
 );%0A%0A
@@ -4008,12 +4008,13 @@
 tyle
+Key
 %5D)
- %7B
 %0A   
@@ -4038,16 +4038,19 @@
 tyle
+Key
 %5D = %7B%7D;%0A
     
@@ -4045,22 +4045,16 @@
 %5D = %7B%7D;%0A
-    %7D%0A
     if (
@@ -4068,24 +4068,27 @@
 harMap%5Bstyle
+Key
 %5D%5Bchar%5D)%0A   
@@ -4104,24 +4104,27 @@
 harMap%5Bstyle
+Key
 %5D%5Bchar%5D = th
@@ -4130,25 +4130,33 @@
 his.measure(
-s
+relevantS
 tyle, char);
@@ -4186,16 +4186,19 @@
 ap%5Bstyle
+Key
 %5D%5Bchar%5D;

fix FontMetric charMap caching