import filter from 'lodash/filter'
import map from 'lodash/map'
import find from 'lodash/find'
import React from 'react'
import createReactClass from 'create-react-class'
import DataFrame from 'dataframe'
import Emitter from 'wildemitter'

import partial from './lib/partial'
import download from './lib/download'
import getValue from './lib/get-value'
import PivotTable from './lib/pivot-table.jsx'
import Dimensions from './lib/dimensions.jsx'
import ColumnControl from './lib/column-control.jsx'
import SoloControl from './lib/solo-control.jsx'
import {
  serializeSoloValue,
  createSoloFilter,
  soloEntries
} from './lib/solo-utils.js'

const _ = { filter, map, find }

export default createReactClass({
  displayName: 'ReactPivot',
  getDefaultProps: function() {
    return {
      rows: [],
      dimensions: [],
      activeDimensions: [],
      reduce: function() {},
      tableClassName: '',
      csvDownloadFileName: 'table.csv',
      csvTemplateFormat: false,
      defaultStyles: true,
      nPaginateRows: 25,
      solo: {},
      hiddenColumns: [],
      hideRows: null,
      sortBy: null,
      sortDir: 'asc',
      eventBus: new Emitter,
      compact: false,
      excludeSummaryFromExport: false,
      onData: function () {},
      soloText: "solo",
      unsoloText: "unsolo",
      subDimensionText: "Sub Dimension...",
      showClearFilters: false,
      clearFiltersText: "Clear Filters"
    }
  },

  getInitialState: function() {
    var allDimensions = this.props.dimensions
    var activeDimensions =  _.filter(this.props.activeDimensions, function (title) {
      return _.find(allDimensions, function(col) {
        return col.title === title
      })
    })

    return {
      dimensions: activeDimensions,
      calculations: {},
      sortBy: this.props.sortBy,
      sortDir: this.props.sortDir,
      hiddenColumns: this.props.hiddenColumns,
      solo: this.props.solo,
      filtersPaused: false,
      hideRows: this.props.hideRows,
      rows: []
    }
  },

  componentDidMount: function() {
    if (this.props.defaultStyles) loadStyles()

    this.dataFrame = DataFrame({
      rows: this.props.rows,
      dimensions: this.props.dimensions,
      reduce: this.props.reduce
    })

    this.updateRows()
  },

  componentDidUpdate: function(prevProps) {
     if(this.props.hiddenColumns !== prevProps.hiddenColumns) {
         this.setHiddenColumns(this.props.hiddenColumns);
     }

    if(this.props.rows !== prevProps.rows) {
      this.dataFrame = DataFrame({
        rows: this.props.rows,
        dimensions: this.props.dimensions,
        reduce: this.props.reduce
      })

      this.updateRows()
    }

    if (this.props.solo !== prevProps.solo) {
      this.setState({solo: this.props.solo}, this.updateRows)
    }
  },

  getColumns: function() {
    var self = this
    var columns = []

    this.state.dimensions.forEach(function(title) {
      var d =  _.find(self.props.dimensions, function(col) {
        return col.title === title
      })

      columns.push({
        type: 'dimension', title: d.title, value: d.value,
        className: d.className, template: d.template, sortBy: d.sortBy
      })
    })

    this.props.calculations.forEach(function(c) {
      if (self.state.hiddenColumns.indexOf(c.title) >= 0) return

      var className = 'reactPivot-calculation'
      if (c.className) className += ' ' + c.className

      columns.push({
        type:'calculation', title: c.title, template: c.template,
        value: c.value, className: className, sortBy: c.sortBy
      })
    })

    return columns
  },

  renderFilterActions: function() {
    var hasFilters = soloEntries(this.state.solo).length > 0

    if (!hasFilters) {
      return <SoloControl solo={this.state.solo} onToggle={this.setSolo} />
    }

    var buttonText = this.state.filtersPaused ? 'Resume Filters' : 'Pause Filters'

    return (
      <div className='reactPivot-filterActions'>
        <SoloControl solo={this.state.solo} onToggle={this.setSolo} />
        {this.props.showClearFilters && (
          <button
            className='reactPivot-clearFilters'
            onClick={this.clearSolo}
          >
            {this.props.clearFiltersText}
          </button>
        )}
        <button onClick={this.toggleFilters}>
          {buttonText}
        </button>
      </div>
    )
  },

  render: function() {
    var html = (
      <div className='reactPivot'>

        <div className='reactPivot-toolbar'>
          { this.props.hideDimensionFilter ? null :
            <Dimensions
              dimensions={this.props.dimensions}
              subDimensionText={this.props.subDimensionText}
              selectedDimensions={this.state.dimensions}
              onChange={this.setDimensions} />
          }

          <div className='reactPivot-controls'>
            <ColumnControl
              hiddenColumns={this.state.hiddenColumns}
              onChange={this.setHiddenColumns} />

            {this.renderFilterActions()}

            <div className="reactPivot-csvExport">
              <button onClick={partial(this.downloadCSV, this.state.rows)}>
                Export CSV
              </button>
            </div>
          </div>
        </div>

        <PivotTable
          columns={this.getColumns()}
          rows={this.state.rows}
          sortBy={this.state.sortBy}
          sortDir={this.state.sortDir}
          onSort={this.setSort}
          onColumnHide={this.hideColumn}
          nPaginateRows={this.props.nPaginateRows}
          tableClassName={this.props.tableClassName}
          onSolo={this.setSolo}
          soloText={this.props.soloText}
          unsoloText={this.props.unsoloText}
          solo={this.state.solo}
        />
      </div>
    )

    return html
  },

  updateRows: function () {
    var columns = this.getColumns()

    var sortByTitle = this.state.sortBy
    var sortCol = _.find(columns, function(col) {
      return col.title === sortByTitle
    }) || {}
    var sortBy = sortCol.sortBy || (sortCol.type === 'dimension' ? sortCol.title : sortCol.value);
    var sortDir = this.state.sortDir
    var hideRows = this.state.hideRows

    var calcOpts = {
      dimensions: this.state.dimensions,
      sortBy: sortBy,
      sortDir: sortDir,
      compact: this.props.compact
    }

    if (!this.state.filtersPaused) {
      calcOpts.filter = createSoloFilter(this.state.solo, this.state.dimensions)
    }

    var rows = this.dataFrame
      .calculate(calcOpts)
      .filter(function (row) { return hideRows ? !hideRows(row) : true })

    this.setState({rows: rows})
    this.props.onData(rows)
  },

  setDimensions: function (updatedDimensions) {
    this.props.eventBus.emit('activeDimensions', updatedDimensions)
    this.setState({dimensions: updatedDimensions})
    setTimeout(this.updateRows, 0)
  },

  setHiddenColumns: function (hidden) {
    this.props.eventBus.emit('hiddenColumns', hidden)
    this.setState({hiddenColumns: hidden})
    setTimeout(this.updateRows, 0)
  },

  setSort: function(cTitle) {
    var sortBy = this.state.sortBy
    var sortDir = this.state.sortDir
    if (sortBy === cTitle) {
      sortDir = (sortDir === 'asc') ? 'desc' : 'asc'
    } else {
      sortBy = cTitle
      sortDir = 'asc'
    }

    this.props.eventBus.emit('sortBy', sortBy)
    this.props.eventBus.emit('sortDir', sortDir)
    this.setState({sortBy: sortBy, sortDir: sortDir})
    setTimeout(this.updateRows, 0)
  },

  setSolo: function(solo) {
    if (!solo || typeof solo !== 'object') return

    var dimension = solo.title
    if (!dimension) return

    var valueKey = serializeSoloValue(solo.value)
    if (!valueKey) return

    var newSolo = Object.assign({}, this.state.solo)
    var valueMap = newSolo[dimension] || {}

    if (Object.prototype.hasOwnProperty.call(valueMap, valueKey)) {
      newSolo[dimension] = this.removeSoloValue(valueMap, valueKey)
      if (!newSolo[dimension]) delete newSolo[dimension]
    } else {
      newSolo[dimension] = this.addSoloValue(valueMap, valueKey)
    }

    this.props.eventBus.emit('solo', newSolo)

    // Auto-resume filters when adding or removing a solo value
    this.setState({solo: newSolo, filtersPaused: false}, this.updateRows)
  },

  clearSolo: function() {
    this.props.eventBus.emit('solo', {})
    this.setState({solo: {}, filtersPaused: false}, this.updateRows)
  },

  addSoloValue: function(valueMap, key) {
    var updated = Object.assign({}, valueMap)
    updated[key] = true
    return updated
  },

  removeSoloValue: function(valueMap, key) {
    var updated = Object.assign({}, valueMap)
    delete updated[key]
    return Object.keys(updated).length > 0 ? updated : null
  },

  toggleFilters: function() {
    this.setState({filtersPaused: !this.state.filtersPaused}, this.updateRows)
  },

  hideColumn: function(cTitle) {
    var hidden = this.state.hiddenColumns.concat([cTitle])
    this.setHiddenColumns(hidden)
    setTimeout(this.updateRows, 0)
  },

  downloadCSV: function(rows) {
    var self = this

    var columns = this.getColumns()

    var csv = _.map(columns, 'title')
      .map(JSON.stringify.bind(JSON))
      .join(',') + '\n'

    var maxLevel = this.state.dimensions.length - 1
    var excludeSummary = this.props.excludeSummaryFromExport

    rows.forEach(function(row) {
      if (excludeSummary && (row._level < maxLevel)) return

      var vals = columns.map(function(col) {

        if (col.type === 'dimension') {
          var val = row[col.title]
        } else {
          var val = getValue(col, row)
        }

        if (col.template && self.props.csvTemplateFormat) {
          val = col.template(val)
        }

        return JSON.stringify(val)
      })
      csv += vals.join(',') + '\n'
    })

    download(csv, this.props.csvDownloadFileName, 'text/csv')
  }
})

function loadStyles() {
  if (typeof document === 'undefined') return // SSR safety
  if (document.getElementById('react-pivot-styles')) return // Already loaded
  
  const css = `.reactPivot {
  margin-top: 40px;
  padding: 10px 20px 20px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}

.reactPivot select {
  color: #555;
  height: 28px;
  border: none;
  margin-right: 5px;
  margin-top: 5px;
  background-color: #FFF;
  border: 1px solid #CCC;
}

.reactPivot-results table {
  width: 100%;
  clear: both;
  text-align: left;
  border-spacing: 0;
}

.reactPivot-results th.asc:after,
.reactPivot-results th.desc:after {
  font-size: 50%;
  opacity: 0.5;
}

.reactPivot-results th.asc:after { content: ' ▲' }
.reactPivot-results th.desc:after { content: ' ▼' }

.reactPivot-results td {
  border-top: 1px solid #ddd;
  padding: 8px;
}

.reactPivot-results td.reactPivot-calculation,
.reactPivot-results th.reactPivot-calculation {
  text-align: right;
}

.reactPivot-results td.reactPivot-indent {
  border: none;
}

.reactPivot-results tr:hover td {
  background: #f5f5f5
}

.reactPivot-results tr:hover td.reactPivot-indent {
  background: none;
}

.reactPivot-solo {
  opacity: 0;
  margin-left: 6px;
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
}

.reactPivot-solo:hover {font-weight: bold}
td:hover .reactPivot-solo {opacity: 0.5}

.reactPivot-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 8px;
  margin: 10px 0;
}

.reactPivot-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  align-items: flex-start;
  justify-content: flex-end;
  flex: 0 0 auto;
}

.reactPivot-controls > * {
  margin: 0;
}

.reactPivot-controls select {
  margin: 0;
  width: 120px;
}

.reactPivot-toolbar select {
  margin-top: 0;
}

.reactPivot-csvExport {
  display: flex;
  align-items: flex-start;
  flex: 0 0 auto;
}

.reactPivot-csvExport button {
  background-color: #FFF;
  border: 1px solid #CCC;
  height: 28px;
  color: #555;
  cursor: pointer;
  padding: 0 12px;
  border-radius: 0;
  margin-top: 0;
  white-space: nowrap;
  flex-shrink: 0;
}

.reactPivot-filterActions {
  display: flex;
  gap: 5px;
  align-items: flex-start;
  flex: 0 0 auto;
}

.reactPivot-filterActions button {
  background-color: #FFF;
  border: 1px solid #CCC;
  height: 28px;
  color: #555;
  cursor: pointer;
  padding: 0 12px;
  border-radius: 0;
  margin-top: 0;
  white-space: nowrap;
  flex-shrink: 0;
}

.reactPivot-soloControl {
  display: flex;
  gap: 5px;
  align-items: flex-start;
}

.reactPivot-dimensions {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  padding: 0;
  text-align: left;
  flex: 1 1 300px;
  min-width: 0;
}

.reactPivot-dimensions select {
  margin: 0;
}

.reactPivot-hideColumn { opacity: 0 }

th:hover .reactPivot-hideColumn {
  opacity: 0.5;
  margin-right: 4px;
  margin-bottom: 2px;
}

.reactPivot-hideColumn:hover {
  font-weight: bold;
  cursor: pointer;
}

.reactPivot-pageNumber {
  padding: 2px;
  margin: 4px;
  cursor: pointer;
  color: gray;
  font-size: 14px;
}

.reactPivot-pageNumber:hover {
  font-weight: bold;
  border-bottom: black solid 1px;
  color: black;
}

.reactPivot-pageNumber.is-selected {
  font-weight: bold;
  border-bottom: black solid 1px;
  color: black;
}

.reactPivot-paginate {
  margin-top: 24px;
}`
  
  const style = document.createElement('style')
  style.id = 'react-pivot-styles'
  style.textContent = css
  document.head.appendChild(style)
}
