/*
 * jsPlumb
 *
 * Title:jsPlumb 2.0.2
 *
 * Provides a way to visually connect elements on an HTML page, using SVG.
 *
 * This file contains utility functions that run in browsers only.
 *
 * Copyright (c) 2010 - 2015 jsPlumb (hello@jsplumbtoolkit.com)
 *
 * http://jsplumbtoolkit.com
 * http://github.com/sporritt/jsplumb
 *
 * Dual licensed under the MIT and GPL2 licenses.
 */

import jsPlumb from '../jsPlumb';

const svgAttributeMap = {
  'stroke-linejoin': 'stroke-linejoin',
  'stroke-dashoffset': 'stroke-dashoffset',
  'stroke-linecap': 'stroke-linecap'
};
const STROKE_DASHARRAY = 'stroke-dasharray';
const DASHSTYLE = 'dashstyle';
const LINEAR_GRADIENT = 'linearGradient';
const RADIAL_GRADIENT = 'radialGradient';
const STOP = 'stop';
const STROKE = 'stroke';
const STROKE_WIDTH = 'stroke-width';
const STYLE = 'style';
const NONE = 'none';
const JSPLUMB_GRADIENT = 'jsplumb_gradient_';
const LINE_WIDTH = 'lineWidth';
const ns = {
  svg: 'http://www.w3.org/2000/svg',
  xhtml: 'http://www.w3.org/1999/xhtml'
};

export function matchesSelector(el, selector, ctx) {
  ctx = ctx || el.parentNode;
  const possibles = ctx.querySelectorAll(selector);
  for (let i = 0; i < possibles.length; i++) {
    if (possibles[i] === el) {
      return true;
    }
  }
  return false;
}

export function consume(e, doNotPreventDefault) {
  if (e.stopPropagation) {
    e.stopPropagation();
  } else {
    e.returnValue = false;
  }
  if (!doNotPreventDefault && e.preventDefault) {
    e.preventDefault();
  }
}

/*
 * Function: sizeElement
 * Helper to size and position an element. You would typically use
 * this when writing your own Connector or Endpoint implementation.
 *
 * Parameters:
 *  x - [int] x position for the element origin
 *  y - [int] y position for the element origin
 *  w - [int] width of the element
 *  h - [int] height of the element
 *
 */
export function sizeElement(el, x, y, w, h) {
  if (el) {
    el.style.height = h + 'px';
    el.height = h;
    el.style.width = w + 'px';
    el.width = w;
    el.style.left = x + 'px';
    el.style.top = y + 'px';
  }
}

// was _attr in svg
export function attr(_node, attributes) {
  for (const i in attributes) {
    if (attributes.hasOwnProperty(i)) {
      _node.setAttribute(i, '' + attributes[i]);
    }
  }
}

// was _node in svg
// TODO: get jsPlumb
export function node(name, attributes) {
  attributes = attributes || {};
  attributes.version = '1.1';
  attributes.xmlns = ns.xhtml;
  return jsPlumb.createElementNS(ns.svg, name, null, null, attributes);
}

// was _pos in svg
export function pos(d) {
  return 'position:absolute;left:' + d[0] + 'px;top:' + d[1] + 'px';
}

function _clearGradient(parent) {
  const els = parent.querySelectorAll(' defs,linearGradient,radialGradient');
  for (let i = 0; i < els.length; i++) {
    els[i].parentNode.removeChild(els[i]);
  }
}

function _updateGradient(parent, _node, style, dimensions, uiComponent) {
  const id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.instance.idstamp();
  // first clear out any existing gradient
  _clearGradient(parent);
  // this checks for an 'offset' property in the gradient, and in the absence of it, assumes
  // we want a linear gradient. if it's there, we create a radial gradient.
  // it is possible that a more explicit means of defining the gradient type would be
  // better. relying on 'offset' means that we can never have a radial gradient that uses
  // some default offset, for instance.
  // issue 244 suggested the 'gradientUnits' attribute; without this, straight/flowchart connectors with gradients would
  // not show gradients when the line was perfectly horizontal or vertical.
  let g;
  if (!style.gradient.offset) {
    g = node(LINEAR_GRADIENT, {id: id, gradientUnits: 'userSpaceOnUse'});
  } else {
    g = node(RADIAL_GRADIENT, {id: id});
  }

  const defs = node(DEFS);
  parent.appendChild(defs);
  defs.appendChild(g);

  // the svg radial gradient seems to treat stops in the reverse
  // order to how canvas does it.  so we want to keep all the maths the same, but
  // iterate the actual style declarations in reverse order, if the x indexes are not in order.
  for (let i = 0; i < style.gradient.stops.length; i++) {
    const styleToUse = uiComponent.segment === 1 || uiComponent.segment === 2 ? i : style.gradient.stops.length - 1 - i;
    //stopColor = _ju.convertStyle(style.gradient.stops[styleToUse][1], true),
    const stopColor = style.gradient.stops[styleToUse][1];
    const s = node(STOP, {'offset': Math.floor(style.gradient.stops[i][0] * 100) + '%', 'stop-color': stopColor});

    g.appendChild(s);
  }
  const applyGradientTo = style.strokeStyle ? STROKE : FILL;
  _node.setAttribute(applyGradientTo, 'url(#' + id + ')');
}

export function applyStyles(parent, _node, style, dimensions, uiComponent) {
  _node.setAttribute(FILL, style.fillStyle ? style.fillStyle : NONE);
  _node.setAttribute(STROKE, style.strokeStyle ? style.strokeStyle : NONE);

  if (style.gradient) {
    _updateGradient(parent, _node, style, dimensions, uiComponent);
  } else {
    // make sure we clear any existing gradient
    _clearGradient(parent);
    _node.setAttribute(STYLE, '');
  }

  if (style.lineWidth) {
    _node.setAttribute(STROKE_WIDTH, style.lineWidth);
  }

  // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like
  // the syntax in VML but is actually kind of nasty: values are given in the pixel
  // coordinate space, whereas in VML they are multiples of the width of the stroked
  // line, which makes a lot more sense.  for that reason, jsPlumb is supporting both
  // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from
  // VML, which will be the preferred method.  the code below this converts a dashstyle
  // attribute given in terms of stroke width into a pixel representation, by using the
  // stroke's lineWidth.
  if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) {
    const sep = style[DASHSTYLE].indexOf(',') === -1 ? ' ' : ',';
    const parts = style[DASHSTYLE].split(sep);
    let styleToUse = '';
    parts.forEach(function(p) {
      styleToUse += (Math.floor(p * style.lineWidth) + sep);
    });
    _node.setAttribute(STROKE_DASHARRAY, styleToUse);
  } else if (style[STROKE_DASHARRAY]) {
    _node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]);
  }

  // extra attributes such as join type, dash offset.
  for (const i in svgAttributeMap) {
    if (style[i]) {
      _node.setAttribute(svgAttributeMap[i], style[i]);
    }
  }
}

export function appendAtIndex(svg, path, idx) {
  if (svg.childNodes.length > idx) {
    svg.insertBefore(path, svg.childNodes[idx]);
  } else {
    svg.appendChild(path);
  }
}

export default {
  matchesSelector,
  consume,
  sizeElement,
  attr,
  node,
  pos,
  applyStyles,
  appendAtIndex
};
const DEFS = 'defs';
const FILL = 'fill';