import React, { Component, PropTypes } from 'react';
import reduceCSSCalc from 'reduce-css-calc';
import { PRESENTATION_ATTRIBUTES } from '../util/ReactUtils';
import _ from 'lodash';

class Text extends Component {

  static propTypes = {
    ...PRESENTATION_ATTRIBUTES,
    fit: PropTypes.bool,
    angle: PropTypes.number,
    textAnchor: PropTypes.oneOf(['start', 'middle', 'end', 'inherit']),
    verticalAnchor: PropTypes.oneOf(['start', 'middle', 'end']),
  };

  static defaultProps = {
    x: 0,
    y: 0,
    lineHeight: '1em',
    capHeight: '0.71em', // Magic number from d3
    fit: false,
    textAnchor: 'start',
    verticalAnchor: 'end', // Maintain compat with existing charts / default SVG behavior
  };

  state = {
    wordsByLines: [],
  };

  componentWillMount() {
    this.updateWordsByLines(this.props, true);
  }

  componentWillReceiveProps(nextProps) {
    const calculateWordWidths = (
      this.props.children !== nextProps.children ||
      this.props.style !== nextProps.style
    );
    this.updateWordsByLines(nextProps, calculateWordWidths);
  }

  updateWordsByLines(props, calculateWordWidths) {
    // Only perform calculations if using features that require them (multiline, fit)
    if (props.width || props.fit) {
      if (calculateWordWidths) {
        const { wordsWithComputedWidth, spaceWidth } = this.calculateWordWidths(props);
        this.wordsWithComputedWidth = wordsWithComputedWidth;
        this.spaceWidth = spaceWidth;
      }

      const wordsByLines = this.calculateWordsByLines(
        this.wordsWithComputedWidth,
        this.spaceWidth,
        props.width
      );
      this.setState({ wordsByLines });
    } else {
      const words = props.children ? props.children.toString().split(/\s+/) : [];
      this.setState({ wordsByLines: [{ words }] });
    }
  }

  calculateWordWidths(props) {
    // Calculate length of each word to be used to determine number of words per line
    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
    _.assign(text.style, props.style);
    svg.appendChild(text);
    document.body.appendChild(svg);

    const words = props.children ? props.children.toString().split(/\s+/) : [];
    const wordsWithComputedWidth = words.map(word => {
      text.textContent = word;
      return { word, width: text.getComputedTextLength() };
    });

    text.textContent = '\u00A0'; // Unicode space
    const spaceWidth = text.getComputedTextLength();

    document.body.removeChild(svg);

    return { wordsWithComputedWidth, spaceWidth };
  }

  calculateWordsByLines(wordsWithComputedWidth, spaceWidth, lineWidth) {
    const { fit } = this.props;
    return wordsWithComputedWidth.reduce((result, { word, width }) => {
      const currentLine = result[result.length - 1];

      if (currentLine && (lineWidth == null || fit ||
        (currentLine.width + width + spaceWidth) < lineWidth)) {
        // Word can be added to an existing line
        currentLine.words.push(word);
        currentLine.width += width + spaceWidth;
      } else {
        // Add first word to line or word is too long to fit on existing line
        const newLine = { words: [word], width };
        result.push(newLine);
      }

      return result;
    }, []);
  }

  render() {
    const {
      textAnchor,
      verticalAnchor,
      fit,
      angle,
      lineHeight,
      capHeight,
      ...textProps,
    } = this.props;
    const { wordsByLines } = this.state;
    const { x, y } = textProps;

    let dy;
    switch (verticalAnchor) {
      case 'start':
        dy = reduceCSSCalc(`calc(${capHeight})`);
        break;
      case 'middle':
        dy = reduceCSSCalc(`calc(${capHeight} / 2)`);
        break;
      default:
        dy = 0;
        break;
    }

    const transforms = [];
    if (fit) {
      const lineWidth = wordsByLines[0].width;
      transforms.push(`scale(${this.props.width / lineWidth})`);
    }
    if (angle) {
      transforms.push(`rotate(${angle}, ${x}, ${y})`);
    }
    if (transforms.length) {
      textProps.transform = transforms.join(' ');
    }

    return (
      <text dy={dy} textAnchor={textAnchor} {...textProps}>
      {
        wordsByLines.length === 1 ?
        wordsByLines[0].words.join(' ') :
        wordsByLines.map((line, index) => (
          <tspan x={x} dy={index ? lineHeight : undefined} key={index}>
            {line.words.join(' ')}
          </tspan>
        ))
      }
      </text>
    );
  }
}

export default Text;

@@ -3587,17 +3587,22 @@
     let 
-d
+startD
 y;%0A    s
@@ -3646,33 +3646,38 @@
 start':%0A        
-d
+startD
 y = reduceCSSCal
@@ -3741,25 +3741,30 @@
 e':%0A        
-d
+startD
 y = reduceCS
@@ -3761,37 +3761,101 @@
 = reduceCSSCalc(
-%60calc
+%0A          %60calc($%7B(wordsByLines.length - 1) / 2%7D * -$%7BlineHeight%7D + 
 ($%7BcapHeight%7D / 
@@ -3856,17 +3856,27 @@
 ht%7D / 2)
-%60
+)%60%0A        
 );%0A     
@@ -3912,14 +3912,84 @@
     
-dy = 0
+startDy = reduceCSSCalc(%60calc($%7BwordsByLines.length - 1%7D * -$%7BlineHeight%7D)%60)
 ;%0A  
@@ -4360,16 +4360,8 @@
 text
- dy=%7Bdy%7D
  tex
@@ -4409,86 +4409,8 @@
   %7B%0A
-        wordsByLines.length === 1 ?%0A        wordsByLines%5B0%5D.words.join(' ') :%0A
     
@@ -4486,32 +4486,36 @@
 dex 
-? lineHeight : undefined
+=== 0 ? startDy : lineHeight
 %7D ke

Improve layout of Text with textAnchor / verticalAnchor. Allows easy aligning (centering, bottom-right, etc) of multi-line text.