/**
 * apeman react package for image component.
 * @class ApImage
 */

'use strict'

import React, { PropTypes as types } from 'react'
import ReactDOM from 'react-dom'
import classnames from 'classnames'
import numcal from 'numcal'
import scaledSize from './sizing/scaled_size'
import { ApSpinner } from 'apeman-react-spinner'
import { ApPureMixin } from 'apeman-react-mixin-pure'

/** @lends ApImage */
const ApImage = React.createClass({

  // --------------------
  // Specs
  // --------------------

  propTypes: {
    /** Image scaling policy */
    scale: types.oneOf([
      'fit',
      'fill',
      'none'
    ]),
    /** Image width */
    width: types.oneOfType([ types.number, types.string ]),
    /** Image height */
    height: types.oneOfType([ types.number, types.string ]),
    /** Image src string */
    src: types.string,
    /** Alt test */
    alt: types.string,
    /** Them of spinner */
    spinnerTheme: types.string,
    /** Handler on image load */
    onLoad: types.func,
    /** Handler on image error. */
    onError: types.func
  },

  mixins: [
    ApPureMixin
  ],

  statics: {
    scaledSize,
    zeroIfNaN (value) {
      return isNaN(value) ? 0 : value
    },
    nullIfNaN (value) {
      return isNaN(value) ? null : value
    }
  },

  getInitialState () {
    const s = this
    return {
      imgWidth: null,
      imgHeight: null,
      mounted: false,
      ready: false,
      loading: !!s.props.src,
      error: null
    }
  },

  getDefaultProps () {
    return {
      scale: 'none',
      width: 300,
      height: 150,
      src: null,
      alt: 'NO IMAGE',
      spinnerTheme: ApSpinner.DEFAULT_THEME,
      onLoad: null,
      onError: null
    }
  },

  render () {
    const s = this
    let {
      state,
      props,
      handleLoad,
      handleError
    } = s

    let {
      width = null,
      height = null,
      src,
      alt
    } = props

    let {
      mounted, error, ready, loading, imgWidth, imgHeight
    } = state
    let className = classnames('ap-image', props.className, {
      'ap-image-loading': src && loading,
      'ap-image-ready': src && ready
    })
    return (
      <div className={ className }
           style={ Object.assign({ width, height }, props.style) }>
        {
          mounted && error ? (
            <ApImage.Alt alt={ alt }/>
          ) : null
        }
        {
          mounted && !error ? (
            <ApImage.Img alt={ alt }
                         src={ src }
                         { ...{ imgWidth, imgHeight, handleLoad, handleError } }
            />
          ) : null
        }
        { loading ? (
          <ApSpinner className='ap-image-spinner'
                     size='xx-large'
                     theme={ props.spinnerTheme }
                     style={ { width, height } }/>
        ) : null }
      </div>
    )
  },

  // --------------------
  // Lifecycle
  // --------------------

  componentWillMount () {
    const s = this
  },

  componentDidMount () {
    const s = this
    s.setState({
      mounted: true
    })
    s._resizeTimer = setTimeout(() => {
      if (!s.state.mounted) {
        return
      }
      s.elm = ReactDOM.findDOMNode(s)
      s.resizeImage()
    }, 0)
  },

  componentWillUnmount () {
    const s = this
    s.setState({
      mounted: false
    })
    s.elm = null
    clearTimeout(s._resizeTimer)
  },

  componentWillReceiveProps (nextProps) {
    const s = this
    let { props } = s
    let srcChanged = nextProps.src !== props.src
    if (srcChanged) {
      s.setState({
        ready: false,
        loading: true,
        error: null
      })
      setTimeout(() => s.resizeImage(), 0)
      return
    }
    let widthChanged = nextProps.width !== props.width
    let heightChanged = nextProps.height !== props.height
    if (widthChanged || heightChanged) {
      setTimeout(() => s.resizeImage(), 0)
    }
  },

  // ------------------
  // Helper
  // ------------------

  handleLoad (e) {
    const s = this
    let { props } = s
    let { width, height } = e.target
    setTimeout(() => s.resizeImage(width, height), 0)

    s.setState({
      error: null,
      ready: true,
      loading: false
    })

    if (props.onLoad) {
      props.onLoad(e)
    }
  },

  handleError (e) {
    const s = this
    let { props } = s

    s.setState({
      error: e,
      loading: false
    })

    if (props.onError) {
      props.onError(e)
    }
  },

  resizeImage (imgContentWidth, imgContentHeight) {
    const s = this
    let { state, props, elm } = s
    if (!elm) {
      return false
    }

    imgContentWidth = imgContentWidth || state.imgContentWidth
    imgContentHeight = imgContentHeight || state.imgContentHeight

    let valid = imgContentWidth && imgContentHeight
    if (!valid) {
      return
    }

    let frameSize = {
      width: elm.offsetWidth,
      height: elm.offsetHeight
    }
    let contentSize = {
      height: imgContentHeight,
      width: imgContentWidth
    }
    let scaledSize = ApImage.scaledSize(
      contentSize, frameSize, props.scale
    )

    s.setState({
      imgContentWidth,
      imgContentHeight,
      imgWidth: scaledSize.width || imgContentWidth,
      imgHeight: scaledSize.height || imgContentHeight,
      ready: true,
      loading: false
    })
  },

  elm: null

})

Object.assign(ApImage, {
  Img ({
    src, alt, imgWidth, imgHeight, handleLoad, handleError
  }) {
    let { nullIfNaN } = ApImage

    let style = {
      width: nullIfNaN(imgWidth),
      height: nullIfNaN(imgHeight)
    }
    return (
      <img src={ src }
           alt={ alt }
           className={ classnames('ap-image-content') }
           style={ style }
           onLoad={ handleLoad }
           onError={ handleError }
      />
    )
  },

  Alt ({ height, alt }) {
    let style = {
      lineHeight: `${height}px`,
      fontSize: `${numcal.min(height * 0.4, 18)}px`
    }
    return (
      <div className="ap-image-notfound"
           style={ style }
      >{ alt }</div>
    )
  }
})

export default ApImage
