text: // Base object for different progress bar shapes
  
  var Path = require('./path');
  var utils = require('./utils');
  
  var DESTROYED_ERROR = 'Object is destroyed';
  
  var Shape = function Shape(container, opts) {
      // Throw a better error if progress bars are not initialized with `new`
      // keyword
      if (!(this instanceof Shape)) {
          throw new Error('Constructor was called without new keyword');
      }
  
      // Prevent calling constructor without parameters so inheritance
      // works correctly. To understand, this is how Shape is inherited:
      //
      //   Line.prototype = new Shape();
      //
      // We just want to set the prototype for Line.
      if (arguments.length === 0) {
          return;
      }
  
      // Default parameters for progress bar creation
      this._opts = utils.extend({
          color: '#555',
          strokeWidth: 1.0,
          trailColor: null,
          trailWidth: null,
          fill: null,
          text: {
              style: {
                  color: null,
                  position: 'absolute',
                  left: '50%',
                  top: '50%',
                  padding: 0,
                  margin: 0,
                  transform: {
                      prefix: true,
                      value: 'translate(-50%, -50%)'
                  }
              },
              autoStyleContainer: true,
              alignToBottom: true,
              value: '',
              className: 'progressbar-text'
          },
          svgStyle: {
              display: 'block',
              width: '100%'
          }
      }, opts, true);  // Use recursive extend
  
      // If user specifies e.g. svgStyle or text style, the whole object
      // should replace the defaults to make working with styles easier
      if (utils.isObject(opts) && opts.svgStyle !== undefined) {
          this._opts.svgStyle = opts.svgStyle;
      }
      if (utils.isObject(opts) && utils.isObject(opts.text) && opts.text.style !== undefined) {
          this._opts.text.style = opts.text.style;
      }
  
      var svgView = this._createSvgView(this._opts);
  
      var element;
      if (utils.isString(container)) {
          element = document.querySelector(container);
      } else {
          element = container;
      }
  
      if (!element) {
          throw new Error('Container does not exist: ' + container);
      }
  
      this._container = element;
      this._container.appendChild(svgView.svg);
      this._warnContainerAspectRatio(this._container);
  
      if (this._opts.svgStyle) {
          utils.setStyles(svgView.svg, this._opts.svgStyle);
      }
  
      // Expose public attributes before Path initialization
      this.svg = svgView.svg;
      this.path = svgView.path;
      this.trail = svgView.trail;
      this.text = null;
  
      var newOpts = utils.extend({
          attachment: undefined,
          shape: this
      }, this._opts);
      this._progressPath = new Path(svgView.path, newOpts);
  
      if (utils.isObject(this._opts.text) && this._opts.text.value) {
          this.setText(this._opts.text.value);
      }
  };
  
  Shape.prototype.animate = function animate(progress, opts, cb) {
      if (this._progressPath === null) {
          throw new Error(DESTROYED_ERROR);
      }
  
      this._progressPath.animate(progress, opts, cb);
  };
  
  Shape.prototype.stop = function stop() {
      if (this._progressPath === null) {
          throw new Error(DESTROYED_ERROR);
      }
  
      // Don't crash if stop is called inside step function
      if (this._progressPath === undefined) {
          return;
      }
  
      this._progressPath.stop();
  };
  
  Shape.prototype.destroy = function destroy() {
      if (this._progressPath === null) {
          throw new Error(DESTROYED_ERROR);
      }
  
      this.stop();
      this.svg.parentNode.removeChild(this.svg);
      this.svg = null;
      this.path = null;
      this.trail = null;
      this._progressPath = null;
  
      if (this.text !== null) {
          this.text.parentNode.removeChild(this.text);
          this.text = null;
      }
  };
  
  Shape.prototype.set = function set(progress) {
      if (this._progressPath === null) {
          throw new Error(DESTROYED_ERROR);
      }
  
      this._progressPath.set(progress);
  };
  
  Shape.prototype.value = function value() {
      if (this._progressPath === null) {
          throw new Error(DESTROYED_ERROR);
      }
  
      if (this._progressPath === undefined) {
          return 0;
      }
  
      return this._progressPath.value();
  };
  
  Shape.prototype.setText = function setText(newText) {
      if (this._progressPath === null) {
          throw new Error(DESTROYED_ERROR);
      }
  
      if (this.text === null) {
          // Create new text node
          this.text = this._createTextContainer(this._opts, this._container);
          this._container.appendChild(this.text);
      }
  
      // Remove previous text and add new
      if (utils.isObject(newText)) {
          utils.removeChildren(this.text);
          this.text.appendChild(newText);
      } else {
          this.text.innerHTML = newText;
      }
  };
  
  Shape.prototype._createSvgView = function _createSvgView(opts) {
      var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
      this._initializeSvg(svg, opts);
  
      var trailPath = null;
      // Each option listed in the if condition are 'triggers' for creating
      // the trail path
      if (opts.trailColor || opts.trailWidth) {
          trailPath = this._createTrail(opts);
          svg.appendChild(trailPath);
      }
  
      var path = this._createPath(opts);
      svg.appendChild(path);
  
      return {
          svg: svg,
          path: path,
          trail: trailPath
      };
  };
  
  Shape.prototype._initializeSvg = function _initializeSvg(svg, opts) {
      svg.setAttribute('viewBox', '0 0 100 100');
  };
  
  Shape.prototype._createPath = function _createPath(opts) {
      var pathString = this._pathString(opts);
      return this._createPathElement(pathString, opts);
  };
  
  Shape.prototype._createTrail = function _createTrail(opts) {
      // Create path string with original passed options
      var pathString = this._trailString(opts);
  
      // Prevent modifying original
      var newOpts = utils.extend({}, opts);
  
      // Defaults for parameters which modify trail path
      if (!newOpts.trailColor) {
          newOpts.trailColor = '#eee';
      }
      if (!newOpts.trailWidth) {
          newOpts.trailWidth = newOpts.strokeWidth;
      }
  
      newOpts.color = newOpts.trailColor;
      newOpts.strokeWidth = newOpts.trailWidth;
  
      // When trail path is set, fill must be set for it instead of the
      // actual path to prevent trail stroke from clipping
      newOpts.fill = null;
  
      return this._createPathElement(pathString, newOpts);
  };
  
  Shape.prototype._createPathElement = function _createPathElement(pathString, opts) {
      var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
      path.setAttribute('d', pathString);
      path.setAttribute('stroke', opts.color);
      path.setAttribute('stroke-width', opts.strokeWidth);
  
      if (opts.fill) {
          path.setAttribute('fill', opts.fill);
      } else {
          path.setAttribute('fill-opacity', '0');
      }
  
      return path;
  };
  
  Shape.prototype._createTextContainer = function _createTextContainer(opts, container) {
      var textContainer = document.createElement('div');
      textContainer.className = opts.text.className;
  
      var textStyle = opts.text.style;
      if (textStyle) {
          if (opts.text.autoStyleContainer) {
              container.style.position = 'relative';
          }
  
          utils.setStyles(textContainer, textStyle);
          // Default text color to progress bar's color
          if (!textStyle.color) {
              textContainer.style.color = opts.color;
          }
      }
  
      this._initializeTextContainer(opts, container, textContainer);
      return textContainer;
  };
  
  // Give custom shapes possibility to modify text element
  Shape.prototype._initializeTextContainer = function(opts, container, element) {
      // By default, no-op
      // Custom shapes should respect API options, such as text.style
  };
  
  Shape.prototype._pathString = function _pathString(opts) {
      throw new Error('Override this function for each progress bar');
  };
  
  Shape.prototype._trailString = function _trailString(opts) {
      throw new Error('Override this function for each progress bar');
  };
  
  Shape.prototype._warnContainerAspectRatio = function _warnContainerAspectRatio(container) {
      if (!this.containerAspectRatio) {
          return;
      }
  
      var computedStyle = window.getComputedStyle(container, null);
      var width = parseFloat(computedStyle.getPropertyValue('width'), 10);
      var height = parseFloat(computedStyle.getPropertyValue('height'), 10);
      if (!utils.floatEquals(this.containerAspectRatio, width / height)) {
          console.warn(
              'Incorrect aspect ratio of container',
              this._container,
              'detected:',
              computedStyle.getPropertyValue('width') + '(width)',
              '/',
              computedStyle.getPropertyValue('height') + '(height)',
              '=',
              width / height
          );
  
          console.warn(
              'Aspect ratio of should be',
              this.containerAspectRatio
          );
      }
  };
  
  module.exports = Shape;
  