/**
 * @author george michael brower / http://georgemichaelbrower.com/
 */

function loadSVG(loc, success, fail) {
  var xmlhttp = new XMLHttpRequest();
  xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == 4) {
      if (xmlhttp.status == 200) {
        if (xmlhttp.responseXML == null) {
          if (fail != undefined) fail.call(this, loc);
        } else {
          var node = xmlhttp.responseXML.getElementsByTagName("svg").item(0);
          var svg = new SVG(node);
          svg.filename = loc;
          success.call(this, svg, loc);
        }
      } else {
        if (fail != undefined) fail.call(this, loc);
      }

    }
  }
  xmlhttp.open("GET", loc, true);
  xmlhttp.send(null);
}

function createSVG(svgText) {
  var node = document.createElement("svg");
  node.innerHTML = svgText;
  return new SVG(node);
}

var SVG = function(node) {
  this.filename = "";

  this.children = [];

  // TODO interpret things other than pixels.

  var w = node.getAttribute("width");
  var h = node.getAttribute("height");

  this.width = w == null ? 0 : parseFloat(w.replace("px", ""));
  this.height = h == null ? 0 : parseFloat(h.replace("px", ""));

  for (var i = 0; i < node.childNodes.length; i++) {

    if (!node.childNodes.item(i).getAttribute) continue;

    var toAdd;
    if (node.childNodes.item(i).nodeName == "g") {
      toAdd = new SVG(node.childNodes.item(i));
    } else {
      toAdd = new Path(node.childNodes.item(i));
    }

    this.children.push(toAdd);

  }

  // Draws every path in this SVG to the specified context.
  this.draw = function(context) {
    for (var i = 0; i < this.children.length; i++) {
      this.children[i].draw(context);
    }
  };


}

var Path = function(element) {

  var _this = this;

  this.element = element;
  this.commands = commands(element);
  this.lineWidth = parseLineWidth(element);
  this.strokeStyle = parseStrokeStyle(element);
  this.fillStyle = parseFillStyle(element);

// for at
  this.totalLength = 0;
  this.lengths = [];
  this.tlengths = []


  var turtle = function() {
    this.x;
    this.y;
    this.x1;
    this.y1;
    this.x2;
    this.y2;
    this.reset = function() {
      this.x = this.y = this.x1 = this.y1 = this.x2 = this.y2 = 0;
    }
    this.reset();
  }

  // Draws this entire path to the specified context.
  this.draw = function(context) {
    this.style(context);
    context.beginPath();
    this.shape(context);
    this.end(context);

  };

  // Calls canvas shape methods such as moveTo(), lineTo(), bezierCurveTo() based on the commands in this path.
  this.shape = function(context) {
    for (var i = 0; i < this.commands.length; i++) {
      this.commands[i].shape(turtle, context);
    }
  };

  this.lerp = function(a, b, c, d, t) {
    var t1 = 1.0 - t;
    return a * t1 * t1 * t1 + 3 * b * t * t1 * t1 + 3 * c * t * t * t1 + d * t * t * t;

  };

  this.at = function(t, c) {
    var rx, ry;
    if (this.lengths.length == 0) {
      this.calcLengths(c);
    }

    var tt = this.tlengths[0];
    var i = 0;

    while (t > tt) {
      i++;
      tt += this.tlengths[i];
    }

    pt = tt - this.tlengths[i];


    var it = this.map(t, pt, tt, 0, 1);


    for (var j = 0; j <= i; j++) {
      this.commands[j].shape(turtle, c);
    }
    var px = turtle.x;
    var py = turtle.y;

    this.commands[i + 1].shape(turtle, c);


    rx = this.lerp(px, turtle.x1, turtle.x2, turtle.x, it);
    ry = this.lerp(py, turtle.y1, turtle.y2, turtle.y, it);

    return {x:rx, y:ry};
  };

  this.map = function(v, i1, i2, o1, o2) {
    return o1 + (o2 - o1) * ((v - i1) / (i2 - i1));
  };


  this.calcLengths = function(c) {
    var rx,ry;
    var prx, pry;

    // go through and get the length of the entire path
    // as well as the lengths of each indiv. path

    var curLength = 0;

    var lengthAccuracy = 0.001;

    this.commands[0].shape(turtle, c);

    var px = prx = turtle.x;
    var py = pry = turtle.y;

    for (var i = 1; i < this.commands.length; i++) {

      curLength = 0;

      px = turtle.x;
      py = turtle.y;
      this.commands[i].shape(turtle, c);

      for (var tt = 0; tt <= 1; tt += lengthAccuracy) {

        rx = this.lerp(px, turtle.x1, turtle.x2, turtle.x, tt);
        ry = this.lerp(py, turtle.y1, turtle.y2, turtle.y, tt);


        curLength += this.dist(rx, ry, prx, pry);


        prx = rx;
        pry = ry;

      }

      this.lengths.push(curLength);
      this.totalLength += curLength;


    }

    for (var j = 0; j < this.lengths.length; j++) {
      this.tlengths.push(this.lengths[j] / this.totalLength);
    }
  }

  this.dist = function (x, y, xx, yy) {
    return Math.sqrt((x - xx) * (x - xx) + (y - yy) * (y - yy));
  };

  // Sets the drawing style of the canvas context based on the styles in this Path.
  this.style = function(context) {


    if (_this.strokeStyle != undefined) {
      context.strokeStyle = _this.strokeStyle;

//		  if (_this.lineWidth == undefined) {
//        	context.lineWidth = 1;
//		  } else {
      context.lineWidth = _this.lineWidth;
//      console.log(context.lineWidth);
//      }
    }

    if (_this.fillStyle != undefined) {
      context.fillStyle = this.fillStyle;
    }

  };

  // Calls context.fill() and/or context.stroke() depending on the styles in this Path.
  this.end = function(context) {
    if (_this.fillStyle != undefined) context.fill();
    if (_this.strokeStyle != undefined) {
      //console.log(context.lineWidth);
      context.stroke();
    }
  }

}

var parseLineWidth = function(element) {
  var a = element.attributes.getNamedItem("stroke-width");
  return a == null ? null : parseFloat(a.nodeValue);
}

var parseStrokeStyle = function(element) {
  var a = element.attributes.getNamedItem("stroke");
  return a == null ? null : a.nodeValue;
}

var parseFillStyle = function(element) {
  var a = element.attributes.getNamedItem("fill");
  if (a == null) {
    var s = element.attributes.getNamedItem("stroke");
    if (s != null) {
      return null;
    } else {
      return "#000000";
    }
  } else {
    if (a.nodeValue == "none") return null;
    return a.nodeValue;
  }
}

var Command = function(type, data) {
  this.type = type;
  this.data = data;
  this.debug = false;

  // Calls context shape methods such as moveTo(), lineTo(), bezierCurveTo(), etc.
  this.shape = function(turtle, c) {

    var px = turtle.x;
    var py = turtle.y;

    if (this.type == "M") {

      turtle.x = this.data[0];
      turtle.y = this.data[1];
      if (c) c.moveTo(turtle.x, turtle.y);


    } else if (this.type == "C") {

      turtle.x = this.data[4];
      turtle.y = this.data[5];
      if (c) c.bezierCurveTo(turtle.x1 = this.data[0],
          turtle.y1 = this.data[1],
          turtle.x2 = this.data[2],
          turtle.y2 = this.data[3],
          turtle.x,
          turtle.y);

    } else if (this.type == "c") {

      if (c) c.bezierCurveTo(turtle.x1 = turtle.x + this.data[0],
          turtle.y1 = turtle.y + this.data[1],
          turtle.x2 = turtle.x + this.data[2],
          turtle.y2 = turtle.y + this.data[3],
          turtle.x += this.data[4],
          turtle.y += this.data[5]);

    } else if (this.type == "S") {

      turtle.x = this.data[2];
      turtle.y = this.data[3];
      var dx = turtle.x - turtle.x2;
      var dy = turtle.y - turtle.y2;
      if (c) c.bezierCurveTo(turtle.x1 = turtle.x + dx,
          turtle.y1 = turtle.y + dy,
          turtle.x2 = this.data[0],
          turtle.y2 = this.data[1],
          turtle.x,
          turtle.y);

    } else if (this.type == "s") {

      var dx = turtle.x - turtle.x2;
      var dy = turtle.y - turtle.y2;
      if (c) c.bezierCurveTo(turtle.x1 = turtle.x + dx,
          turtle.y1 = turtle.y + dy,
          turtle.x2 = turtle.x + this.data[0],
          turtle.y2 = turtle.y + this.data[1],
          turtle.x += this.data[2],
          turtle.y += this.data[3]);

    } else if (this.type == "L") {

      turtle.x1 = turtle.x;
      turtle.y1 = turtle.y;
      if (c) c.lineTo(turtle.x = this.data[0],
          turtle.y = this.data[1]);
      turtle.x2 = turtle.x;
      turtle.y2 = turtle.y;

    } else if (this.type == "l") {

      turtle.x1 = turtle.x;
      turtle.y1 = turtle.y;
      if (c) c.lineTo(turtle.x += this.data[0], turtle.y += this.data[1]);
      turtle.x2 = turtle.x;
      turtle.y2 = turtle.y;

    } else if (this.type == "H") {

      turtle.x1 = turtle.x;
      turtle.y1 = turtle.y;
      if (c) c.lineTo(turtle.x = this.data[0], turtle.y)
      turtle.x2 = turtle.x;
      turtle.y2 = turtle.y;

    } else if (this.type == "h") {

      turtle.x1 = turtle.x;
      turtle.y1 = turtle.y;
      if (c) c.lineTo(turtle.x += this.data[0], turtle.y)
      turtle.x2 = turtle.x;
      turtle.y2 = turtle.y;

    } else if (this.type == "V") {

      turtle.x1 = turtle.x;
      turtle.y1 = turtle.y;
      if (c) c.lineTo(turtle.x, turtle.y = this.data[0]);
      turtle.x2 = turtle.x;
      turtle.y2 = turtle.y;

    } else if (this.type == "v") {

      turtle.x1 = turtle.x;
      turtle.y1 = turtle.y;
      if (c) c.lineTo(turtle.x, turtle.y += this.data[0]);
      turtle.x2 = turtle.x;
      turtle.y2 = turtle.y;

    } else if (this.type == "z") {

      c.closePath();

    } else {

      alert("unrecognized command " + this.type);

    }

    if (c) {
      //c.strokeStyle = "#000000";
      if (this.debug) {
        c.strokeRect(turtle.x - 1.5, turtle.y - 1.5, 3, 3);
        c.beginPath();
        c.moveTo(turtle.px, turtle.py);
        c.lineTo(turtle.x1, turtle.y1);
        c.closePath();
        c.stroke();
      }
    }

  }

}

// Utility functions

var commands = function(element) {

  if (element.nodeName.toLowerCase() == "path") {
    return commandsFromD(element.getAttribute("d"));
  }

  if (element.nodeName.toLowerCase() == "polygon") {
    return commandsFromPoints(element.getAttribute("points"));
  }

  if (element.nodeName.toLowerCase() == "line") {
    return commandsFromLine(element);
  }

  if (element.nodeName.toLowerCase() == "rect") {
    return commandsFromRect(element);
  }

  return [];

}

// Returns an array of commands as interpreted by the "d" attribute of a path.
var commandsFromD = function(d) {

  var toReturn = [];
  var commands = d.match(/[a-zA-Z][0-9\.\-\,]+/g);

  for (var i = 0; i < commands.length; i++) {

    var type = commands[i].charAt(0);

    // Dirty time.
    var commandData = commands[i].substr(1);
    commandData = commandData.replace(/\-/g, ",-")

    if (commandData.charAt(0) == ",") {
      commandData = commandData.substr(1);
    }
    commandData = commandData.split(",");
    for (var j = 0; j < commandData.length; j++) {
      commandData[j] = parseFloat(commandData[j]);
    }

    toReturn.push(new Command(type, commandData));

  }

  return toReturn;
}

var commandsFromLine = function(element) {
  var toReturn = [];
  var x1 = parseFloat(element.getAttribute("x1"));
  var x2 = parseFloat(element.getAttribute("x2"));
  var y1 = parseFloat(element.getAttribute("y1"));
  var y2 = parseFloat(element.getAttribute("y2"));
  toReturn.push(new Command("M", [x1,y1]));
  toReturn.push(new Command("L", [x2,y2]));
  return toReturn;
}

// Returns an array of commands as interpreted by the "points" attribute of a polygon.
var commandsFromPoints = function(pointAttribute) {
  //pointAttribute = pointAttribute.replace(/\,\-/g, "-");


  var shouldBeComma = true;
  if (pointAttribute.indexOf(",") == -1) {
    for (var i = 0; i < pointAttribute.length; i++) {
      var c = pointAttribute.charAt(i);
      if (c == " ") {
        if (shouldBeComma) {
          pointAttribute = pointAttribute.setCharAt(i, ",");
        }
        shouldBeComma = !shouldBeComma;
      }
    }
  }

  pointAttribute = "M" + pointAttribute;
  pointAttribute = pointAttribute.replace(/ /g, "L") + "z";
  var toReturn = commandsFromD(pointAttribute);
  return toReturn;
}

String.prototype.setCharAt = function(index, chr) {
  if (index > this.length - 1) return str;
  return this.substr(0, index) + chr + this.substr(index + 1);
}

var commandsFromRect = function(element) {

  var toReturn = [];
  var x = parseFloat(element.getAttribute("x"));
  var y = parseFloat(element.getAttribute("y"));
  var w = parseFloat(element.getAttribute("width"));
  var h = parseFloat(element.getAttribute("height"));
  toReturn.push(new Command("M", [x,y]));
  toReturn.push(new Command("h", [w]));
  toReturn.push(new Command("v", [h]));
  toReturn.push(new Command("h", [-w]));
  toReturn.push(new Command("v", [-h]));
  return toReturn;
}
