import React, {Component} from 'react'
import {connect} from 'react-redux'
import classNames from 'classnames/bind'
import {
  MdCheckBox, MdCheckBoxOutlineBlank, MdAddCircle, MdKeyboardArrowDown, MdPerson,
} from 'react-icons/lib/md'
import {ItemsCounter, Count} from './styles.react.scss'
import SelectedItems from 'components/filterable_list/selected_items'
import NonExclusiveField from 'components/non_exclusive_field'
import CommentField from 'components/comment_field'
import FileField from 'components/file_field'
import Filter from 'components/filter'
import Button from 'components/utils/button'
import List from 'components/list'
import SearchBar from 'components/search_bar'
import HorizontalScrollableView from 'components/utils/horizontal_scrollable_view'
import NonExclusiveFieldFilter from 'components/non_exclusive_field_filter'
import SecondaryActions from 'components/utils/secondary_actions'
import {tolerant_equal} from 'selectors/base'
import {get_fields} from 'selectors/fields'
import {
  action_create_value, new_values_fuse, new_comments_fuse, allow_creating_value,
} from 'common/fields'
import store from 'common/store'
import auto_bind from 'common/auto_bind'
import request from 'common/request'
import persistent_state from 'common/persistent_state'
import event_system from 'common/event_system'
import {
  range, set_from_array, array_contains, make_persistent, map_hash, for_all, array_from_set,
  array_sum, empty_set, set_count,
} from 'common/utilities'
import {alert_failure, alert_warning, show_popover, dismiss_popover} from 'actions/display'
import {update_field, remove_field} from 'actions/fields'

const cx = classNames.bind(require('styles/filterable_table.scss'))

const get_values_with_selection = make_persistent(
  (values, selected, locked, existing_values) => {
    const existing_value_ids = {}
    existing_values.forEach((values) => values.forEach(({id}) => existing_value_ids[id] = true))
    return values.filter(({id}) => existing_value_ids[id] || selected[id] || locked[id])
    .map((value) => ({
      ...value,
      selected: selected[value.id],
      locked: locked[value.id],
    }))
  }
)

const compare = (a, b, null_values = [], ascending = true) => {
  let is_null = (c) => array_contains([null, undefined, ...null_values], c, true)
  let a_is_null = is_null(a), b_is_null = is_null(b)
  if (b_is_null) {
    if (a_is_null) {
      return 0
    } else {
      return -1
    }
  } else {
    if (a_is_null) {
      return 1
    } else {
      if (a > b) {
        return ascending ? 1 : -1
      } else if (a < b) {
        return ascending ? -1 : 1
      } else {
        return 0
      }
    }
  }
}

let counter = 0

class FilterableTable extends Component {
  constructor(props) {
    super(props)
    this.state = {
      comment_filtered_item_ids: {},
      value_filters: {},
      order_by: null,
      order_by_field: null,
      ascending: true,
      edit_field_mode: false,
      ...persistent_state.get(props.persistent_state_key),
    }
    auto_bind(this)
  }

  componentDidMount() {
    this.mounted = true
    this.props.get_table_ref(this)
    this.selected_items_box = `filterable_table_selected_items_${counter++}`
  }

  componentWillUnmount() {
    this.mounted = false
    persistent_state.store(this.props.persistent_state_key, this.state)
  }

  componentWillReceiveProps({items, selected}) {
    if (items !== this.props.items || selected !== this.props.selected) {
      event_system.post(this.selected_items_box, array_from_set(selected).map((id) => items[id]))
    }
  }

  start_edit_field(field_id) {
    this.setState({edit_field_mode: field_id})
  }

  on_comment_query_change(field_id, query, results) {
    if (this.mounted && !tolerant_equal(results, this.state.comment_filtered_item_ids[field_id])) {
      this.setState({
        comment_filtered_item_ids: {
          ...this.state.comment_filtered_item_ids,
          [field_id]: results,
        },
      })
    }
  }

  toggle_filtered_value(value) {
    if (this.mounted) {
      this.setState({value_filters: {...this.state.value_filters, [value.id]: !this.state.value_filters[value.id]}})
    }
  }

  order_by(order_by, ascending = null) {
    if (this.mounted) {
      if (this.state.order_by == order_by) {
        this.setState({ascending: ascending === null ? !this.state.ascending : ascending})
      } else {
        this.setState({order_by, ascending: ascending === null ? true : ascending})
      }
    }
  }

  order_by_comment(field_id) {
    if (this.mounted) {
      if (this.state.order_by == 'comment' && this.state.order_by_field == field_id) {
        this.setState({ascending: !this.state.ascending})
      } else {
        this.setState({order_by: 'comment', order_by_field: field_id, ascending: true})
      }
    }
  }

  get_filtered_item_ids() {
    let order = map_hash(this.props.items, (item) => {
      if (this.state.order_by == 'comment') {
        let {comments} = this.props.fields[this.state.order_by_field]
        return comments[item.id]
      } else {
        return item[this.state.order_by]
      }
    })
    return Object.keys(this.state.comment_filtered_item_ids).reduce((filtered_item_ids, field_id) => {
      let results = this.state.comment_filtered_item_ids[field_id]
      if (results) {
        let results_set = set_from_array(results)
        return filtered_item_ids.filter((id) => results_set[id])
      } else {
        return filtered_item_ids
      }
    }, [...array_from_set(this.state.value_filters), ...array_from_set(this.props.locked_values)].reduce((filtered_item_ids, value_id) => {
      return filtered_item_ids.filter(this.props.filter_value(value_id))
    }, this.props.filtered_item_ids))
    .filter((id) => this.props.items[id])
    .sort((id1, id2) => compare(order[id1], order[id2], ["", " "], this.state.ascending))
  }

  view_selected() {
    const {on_card_click, render_card} = this.props
    store.dispatch(show_popover(
      SelectedItems,
      {
        on_card_click,
        render_card,
        box: this.selected_items_box,
      },
      ""
    ))
  }

  render() {
    let {actions, selectable, selected, create_field} = this.props
    let fake_state = {fields: this.props}
    let fields = get_fields(fake_state)
    let filtered_item_ids = this.get_filtered_item_ids()
    let all_selected = for_all(filtered_item_ids, (id) => this.props.selected[id])
    let secondary_actions_active = array_sum(actions.map(({badge}) => badge || 0))
    return (
      <div className={cx('filterable-table', {'filterable-table_selectable': this.props.selectable})}>
        {this.props.title ? (
          <div className={cx('filterable-table__top')}>
            <div className={cx('filterable-table__top-title')}>
              {this.props.title}
            </div>
            <div className={cx('filterable-table__top-actions')}>
              {empty_set(selected) ? null : (
                <ItemsCounter onClick={this.view_selected}>
                  <Count>
                    {set_count(selected)}
                  </Count>
                  <MdPerson />
                </ItemsCounter>
              )}
              {actions.length > 0 ? (
                <SecondaryActions active={secondary_actions_active}>
                  {actions}
                </SecondaryActions>
              ) : null}
              {this.props.main_action ? (
                <Button {...this.props.main_action} />
              ) : null}
            </div>
          </div>
        ) : null}
        <HorizontalScrollableView>
          <div className={cx('filterable-table__main')}>
            <div className={cx('filterable-table__header')}>
              {this.props.selectable ? (
                <div className={cx('filterable-table__all-items')}>
                  <div className={cx('filterable-table__items-count')}>
                    {filtered_item_ids.length}
                  </div>
                  <div className={cx('filterable-table__select-all-checkbox', {'filterable-table__select-all-checkbox_selected': all_selected})} onClick={() => this.props.on_select_all(filtered_item_ids)}>
                    {all_selected ? (
                      <MdCheckBox />
                    ) : (
                      <MdCheckBoxOutlineBlank />
                    )}
                  </div>
                </div>
              ) : null}
              <div className={cx('filterable-table__card-header')}>
                {this.props.children}
              </div>
              <div className={cx('filterable-table__fields-header')}>
                {fields.map((field) => {
                  let props = {
                    key: field.id,
                    id: field.id,
                    name: field.name,
                  }
                  switch (field.category) {
                    case 'non_exclusive':
                      return (
                        <NonExclusiveFieldFilter
                          {...props}
                          values={get_values_with_selection(
                            field.values,
                            this.state.value_filters,
                            this.props.locked_values,
                            filtered_item_ids.map((item_id) => this.props.get_item_field_values(item_id, field.id))
                          )}
                          toggle_filtered_value={this.toggle_filtered_value}
                          className={cx('filterable-table__filter_field')}
                          stop_recycling={() => this.props.stop_recycling(field.id)}
                          />
                      )
                    case 'comment':
                      return (
                        <CommentFieldFilter
                          {...props}
                          comments={Object.keys(field.comments).map((id) => ({id, comment: field.comments[id]}))}
                          default_results={Object.keys(this.props.items)}
                          on_query_change={(query, results) => this.on_comment_query_change(field.id, query, results)}
                          sort_items={() => this.order_by_comment(field.id)}
                          />
                      )
                    case 'file':
                      return (
                        <FileFieldFilter {...props} />
                      )
                    default:
                      return null
                  }
                })}
                {this.props.editable && create_field ? (
                  <Button icon={MdAddCircle} onClick={create_field} discreet className={cx('filterable-table__add-field-button')}>
                    Nouveau Champ
                  </Button>
                ) : null}
              </div>
            </div>
            <div className={cx('filterable-table__rows')}>
              <List item_height={60}>
                {filtered_item_ids.map((id) => (
                  <Item
                    key={id}
                    item={this.props.items[id]}
                    fields={fields}
                    get_item_field_values={this.props.get_item_field_values}
                    add_value={(value_id) => this.props.add_value(id, value_id)}
                    remove_value={(value_id) => this.props.remove_value(id, value_id)}
                    update_comment={(field_id, comment) => this.props.update_comment(id, field_id, comment)}
                    editable={this.props.editable}
                    selectable={this.props.selectable}
                    selected={this.props.selected[id]}
                    render_card={this.props.render_card}
                    on_card_click={() => this.props.on_card_click(id)}
                    item_type={this.props.item_type}
                  />
                ))}
              </List>
            </div>
          </div>
        </HorizontalScrollableView>
      </div>
    )
  }
}

class Item extends Component {
  shouldComponentUpdate(props) {
    return props.item !== this.props.item ||
      props.fields !== this.props.fields ||
      props.selected !== this.props.selected
  }

  render() {
    return (
      <div className={cx('filterable-table__item')}>
        <div className={cx('filterable-table__card', {'filterable-table__card_selected': this.props.selected})} onClick={this.props.on_card_click}>
          {this.props.render_card(this.props.item)}
        </div>
        {this.props.fields.map((field) => {
          let props = {
            key: field.id,
            ...field,
            editable: this.props.editable,
            edit_mode: 'click',
            show_label: false,
          }
          switch (field.category) {
            case "non_exclusive":
              return (
                <ItemNonExclusiveField
                  {...props}
                  selected_values={this.props.get_item_field_values(this.props.item.id, field.id)}
                  add_value={this.props.add_value}
                  remove_value={this.props.remove_value}
                  allow_creating_values={allow_creating_value(this.props.item_type, field)}
                />
              )
            case "comment":
              return (
                <ItemCommentField
                  {...props}
                  comment={field.comments[this.props.item.id]}
                  update_comment={(comment) => this.props.update_comment(field.id, comment)}
                  />
              )
            case "file":
              return (
                <ItemFileField
                  {...props}
                  file={field.files[this.props.item.id]}
                  user_id={this.props.item.id}
                  />
              )
            default:
              return null
          }
        })}
        <div key="dummy1" className={cx('filterable-table__field')}>
        </div>
        <div key="dummy2" className={cx('filterable-table__field')}>
        </div>
      </div>
    )
  }
}

const ItemNonExclusiveField = (props) => (
  <div className={cx('filterable-table__field')}>
    <NonExclusiveField
      {...props}
      ellipsis={3}
      value_ellipsis={65}
      field_ellipsis={150}
      on_select_value={props.add_value}
      on_deselect_value={props.remove_value}
      create_value={(value_name) => new Promise((resolve, reject) => {
        let characters = value_name.split("")
        if (array_contains(characters, ',') || array_contains(characters, ';')) {
          store.dispatch(alert_failure(`Les caractères ',' et ';' sont interdits dans le nom d'une valeur.`))
          reject()
        } else {
          action_create_value(props.id, value_name)
          .then(resolve)
        }
      })}
      />
  </div>
)

const ItemCommentField = (props) => (
  <div className={cx('filterable-table__field')}>
    <CommentField
      {...props}
      comment_ellipsis={300}
      on_submit_comment={props.update_comment}
      />
  </div>
)

const ItemFileField = (props) => (
  <div className={cx('filterable-table__field')}>
    <FileField {...props} />
  </div>
)

class CommentFieldFilter extends Component {
  constructor(props) {
    super(props)
    this.state = {
      active: false,
    }
    this.on_query_change = this.on_query_change.bind(this)
  }

  on_query_change(query, results) {
    if (this.state.active == (query == "")) {
      this.setState({active: !this.state.active})
    }
    this.props.on_query_change(query, results)
  }

  render() {
    return (
      <Filter
        className={cx('filterable-table__filter', 'filterable-table__filter_field')}
        active={this.state.active}
        label={this.props.name}
        onClick={this.props.sort_items}
        >
        <SearchBar
          ref={(search_bar) => this.search_bar = search_bar}
          className={cx('filterable-table__filter-search-bar')}
          extend={false}
          make_fuse={new_comments_fuse}
          items={this.props.comments}
          default_results={this.props.default_results}
          on_query_change={this.on_query_change}
          />
      </Filter>
    )
  }
}

const FileFieldFilter = ({name}) => (
  <Filter
    className={cx('filterable-table__filter', 'filterable-table__filter_field')}
    active={false}
    label={name}
    />
)

FilterableTable.defaultProps = {
  items: {},
  fields: {},
  values: {},
  on_card_click: (item_id) => {},
  editable: true,
  selectable: false,
  shareable_with_users: false,
  selected: {},
  locked_values: {},
  on_select_all: (filtered_item_ids) => {},
  get_item_field_values: (item_id, field_id) => null,
  filter_value: (value_id) => (id) => true,
  actions: [],
  get_table_ref: (table) => {},
  item_type: null,
  persistent_state_key: "",
}

export default FilterableTable
