import React, {Component} from 'react'
import classNames from 'classnames/bind'
import {connect} from 'react-redux'
import {MdArrowBack, MdKeyboardArrowLeft, MdKeyboardArrowRight} from 'react-icons/lib/md'
import Calendar from '../generic_calendar'
import Button from 'components/utils/button'
import TimePickers from './time_pickers'
import {color} from '../../common/constants'
import {
  array_from_hash,
  map_hash,
  set_from_array,
  array_from_set,
  range,
  empty_set,
  for_all,
  filter_hash,
  flatten_array,
  create_hash,
} from '../../common/utilities'
import request from '../../common/request'
import store from '../../common/store'
import auto_bind from '../../common/auto_bind'
import moment from '../../common/moment'
import {events_difference} from '../../common/calendar'
import event_system from '../../common/event_system'
import {get_user_events} from '../../selectors/calendar'
import {get_user_missions_events} from '../../selectors/missions'
import {toggle_item_id} from '../../reducers/base'

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

const chunks_count_in_hour = 4
const chunk_length_in_minutes = 60 / chunks_count_in_hour
const chunks_count_in_day = 24 * chunks_count_in_hour

const day_id = (day) => day.format("YYYY-MM-DD")
const get_periods = (chunk_set) => {
  let periods = [], period_started, start = null
  let add_period = (end) => periods.push({start, end})
  range(chunks_count_in_day).forEach((chunk) => {
    if (chunk_set[chunk]) {
      if (!period_started) {
        start = chunk
        period_started = true
      }
    } else {
      if (period_started) {
        add_period(chunk - 1)
        period_started = false
      }
    }
  })
  if (period_started) {
    add_period(chunks_count_in_day - 1)
  }
  return periods
}
const get_start_end = (day, start_id, end_id) => {
  let start_of_day = moment(day)
  let start = start_id, end = end_id
  if (start > end) {
    start = end
    end = start_id
  }
  return {
    start: moment(start_of_day).add(start * chunk_length_in_minutes, 'minutes'),
    end: moment(start_of_day).add((end + 1) * chunk_length_in_minutes, 'minutes'),
  }
}

class AgencyEditUserCalendarStatic extends Component {
  constructor(props) {
    super(props)
    auto_bind(this)
    let active = {}
    props.agency_events.forEach((event) => {
      let current = moment(event.start)
      while (current.isBefore(event.end)) {
        let id = day_id(current)
        if (!active[id]) {
          active[id] = {}
        }
        active[id][(60 * current.hour() + current.minutes()) / chunk_length_in_minutes] = true
        current.add(chunk_length_in_minutes, 'minutes')
      }
    })
    this.state = {
      active,
      mode: "month",
      week: 0,
    }
  }

  componentDidMount() {
    event_system.register('dismiss_popover', this.update_user_events)
  }

  componentDidUpdate() {
    if (this.should_scroll_to_week) {
      this.calendar.scroll_to_week(this.should_scroll_to_week)
      this.should_scroll_to_week = false
    }
  }

  on_day_click(date) {
    let id = day_id(date)
    if (this.is_full_day(id)) {
      this.setState({
        active: {
          ...this.state.active,
          [id]: this.empty_day(),
        }
      })
    } else if (this.is_empty_day(id)) {
      this.setState({
        active: {
          ...this.state.active,
          [id]: this.full_day(),
        },
      })
    } else {
      this.on_navigate_to_week(date.endOf('week').diff(moment(), 'week'))
    }
  }

  full_day() {
    return set_from_array(range(chunks_count_in_day))
  }

  empty_day() {
    return false
  }

  is_empty_day(id) {
    let day = this.state.active[id]
    return !day || empty_set(day)
  }

  is_full_day(id) {
    let day = this.state.active[id]
    return day && for_all(range(chunks_count_in_day), (chunk) => this.state.active[id][chunk])
  }

  on_create_days_period(start, end, paint = true) {
    let day_ids = [], current = moment(start), condition = paint ? this.is_empty_day : this.is_full_day
    while (current.isBefore(end)) {
      let id = day_id(current)
      if (condition(id)) {
        day_ids.push(id)
      }
      current.add(1, 'day')
    }
    this.setState({active: {
      ...this.state.active,
      ...map_hash(set_from_array(day_ids), paint ? this.full_day : this.empty_day),
    }})
  }

  on_create_chunks_period(date, chunks, paint = true) {
    let id = day_id(date)
    this.setState(({active}) => ({active: {
      ...active,
      [id]: {
        ...(active[id] || {}),
        ...create_hash(chunks, () => paint)
      },
    }}))
  }

  get_day_events() {
    return array_from_set(this.state.active).filter(this.is_full_day).map((day) => ({
      start: moment(day),
      end: moment(day).endOf('day'),
      full_days: true,
    }))
  }

  get_hour_events() {
    let partially_busy_days = array_from_set(this.state.active).filter((id) => !this.is_full_day(id) && !this.is_empty_day(id))
    let events = []
    let add_events = (day, active_chunks) => {
      get_periods(active_chunks).forEach(({start, end}) => {
        events.push({
          ...get_start_end(day, start, end),
          full_days: false,
        })
      })
    }
    partially_busy_days.forEach((day) => add_events(
      day,
      this.state.active[day]
    ))
    return events
  }

  update_user_events() {
    this.props.update_user_events([...this.get_day_events(), ...this.get_hour_events()])
  }

  on_navigate_to_week(week) {
    this.setState({week, mode: "week"})
  }

  on_navigate_to_month() {
    this.setState({mode: "month"})
    this.should_scroll_to_week = this.state.week
  }

  on_next_week() {
    this.setState({week: this.state.week + 1})
  }

  on_previous_week() {
    this.setState({week: this.state.week - 1})
  }

  get_start_of_week() {
    return moment().startOf('week').add(this.state.week, 'weeks')
  }

  render() {
    const {user_events, mission_events} = this.props
    const events = {
      [color('important', 'light')]: user_events,
      [color('primary')]: mission_events,
    }
    switch (this.state.mode) {
      case "week":
        let start_of_week = this.get_start_of_week()
        return (
          <div className={cx('input-calendar', 'input-calendar_week')}>
            <div className={cx('input-calendar__nav')}>
              <Button
                className={cx('input-calendar__nav-back')}
                back
                onClick={this.on_navigate_to_month}
                >
                Retour vue {start_of_week.format(`MMMM${moment().isSame(start_of_week, 'year') ? '' : ' YYYY'}`)}
              </Button>
              <div className={cx('input-calendar__nav-weeks')}>
                <div onClick={this.on_previous_week} className={cx('input-calendar__nav-weeks-control')}>
                  <MdKeyboardArrowLeft />
                </div>
                <div className={cx('input-calendar__nav-weeks-current')}>
                  Semaine {start_of_week.week()}
                </div>
                <div onClick={this.on_next_week} className={cx('input-calendar__nav-weeks-control')}>
                  <MdKeyboardArrowRight />
                </div>
              </div>
            </div>
            <TimePickers
              events={events}
              days={range(7).map((day) => {
                let date = moment(start_of_week).add(day, 'days')
                return {
                  date,
                  chunks: this.state.active[day_id(date)] || {},
                }
              })}
              drag_color={color('attention')}
              full_day_color={color('important')}
              flip={this.on_create_chunks_period}
              />
          </div>
        )
      case "month":
        return (
          <div className={cx('input-calendar', 'input-calendar_month')}>
            <Calendar
              ref={(calendar) => this.calendar = calendar}
              events={{
                ...events,
                [color('attention')]: this.get_hour_events(),
                [color('important')]: this.get_day_events(),
              }}
              on_click={this.on_day_click}
              drag_color={color('important')}
              on_drag_and_drop={this.on_create_days_period}
              on_navigate_to_week={this.on_navigate_to_week}
            />
          </div>
        )
    }
  }
}

const AgencyEditUserCalendar = connect(
  (state, {user_id}) => {
    const {user: user_events, agency: agency_events} = get_user_events(user_id)(state)
    const mission_events = get_user_missions_events(user_id)(state)
    return {
      user_events,
      agency_events,
      mission_events,
    }
  },
  (dispatch, {user_id}) => {
    return {
      update_user_events(new_events) {
        let state = store.getState()
        const {agency: old_events} = get_user_events(user_id)(state)
        let {remove, add} = events_difference(old_events, new_events)
        request.put(`users/${user_id}/events`, {remove_events: (remove || []).map((event) => event.id), events: add})
      },
    }
  }
)(AgencyEditUserCalendarStatic)

export default AgencyEditUserCalendar
