import React, {Component} from 'react'
import classNames from 'classnames/bind'
import {DragSource, DropTarget} from 'react-dnd'
import Hover from 'components/utils/hover'
import {getEmptyImage} from 'react-dnd-html5-backend'
import auto_bind from '../../common/auto_bind'
import moment from '../../common/moment'
import {
  range,
  exist,
  for_all,
  make_memoized,
  set_from_array,
  empty_array,
  map_hash,
  filter_hash,
  array_from_set,
  create_hash,
  group_by_batch,
  property_getter,
} from '../../common/utilities'
import {make_periods, intersecting_periods_interval} from '../../common/calendar'
import {tolerant_selector} from '../../selectors/base'

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

const is_between = (a, b, c) => (a >= b && a < c) || (a >= c && a < b)
export const day_id = (day) => day.format("YYYY-MM-DD")
const get_events_periods = (events) => make_periods(events)
const get_day_periods = make_memoized((day) => tolerant_selector(
  [get_events_periods],
  (periods) => {
    let {lower, upper} = intersecting_periods_interval(periods, moment(day), moment(day).endOf('day'))
    return periods.slice(lower, upper)
  }
))
const get_event_hours = make_memoized((day) => tolerant_selector(
  [get_day_periods(day)],
  (periods) => {
    return set_from_array(range(24).filter((hour) => {
      let start = moment(day).add(hour, 'hours')
      let end = moment(start).endOf('hour')
      let {lower, upper} = intersecting_periods_interval(periods, start, end)
      return lower != upper
    }))
  }
))

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

export 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
}

export 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'),
  }
}

export const get_days_chunks = (events) => {
  const days = {}
  events.forEach((event) => {
    let current = moment(event.start)
    while (current.isBefore(event.end)) {
      let id = day_id(current)
      if (!days[id]) {
        days[id] = {}
      }
      if (!event.full_days) {
        days[id][(60 * current.hour() + current.minutes()) / chunk_length_in_minutes] = true
      }
      current.add(chunk_length_in_minutes, 'minutes')
    }
  })
  return days
}

class TimePickers extends Component {
  constructor(props) {
    super(props)
    auto_bind(this)
  }

  render_displayed_hour(hour) {
    return (
      <div key={hour} className={cx('time-pickers__header-hour')}>
        {hour % 2 == 0 ? `${hour}h` : null}
      </div>
    )
  }

  render_day({date, chunks}) {
    return (
      <div key={date.format()} className={cx('time-pickers__day')}>
        <div className={cx('time-pickers__day-label')}>
          {this.props.day_label ? date.format(`D MMMM${moment().isSame(date, 'year') ? '' : ' YYYY'}`) : ""}
        </div>
        <TimePicker
          color={this.props.full_day_color && for_all(range(chunks_count_in_day), (chunk) => chunks[chunk]) ? this.props.full_day_color : this.props.drag_color}
          on_drag_and_drop={this.props.on_drag_and_drop}
          chunks={chunks}
          date={date}
          events={this.props.events}
          flip={this.props.flip}
          />
      </div>
    )
  }

  render() {
    return (
      <div className={cx('time-pickers')}>
        <div className={cx('time-pickers__header')}>
          {range(25).map(this.render_displayed_hour)}
        </div>
        <div className={cx('time-pickers__days')}>
          {this.props.days.map(this.render_day)}
          <div key={'bottom-filler'} className={cx('time-pickers__days-bottom-filler')}></div>
        </div>
      </div>
    )
  }
}

class TimePicker extends Component {
  constructor(props) {
    super(props)
    this.state = {
      is_dragging: false,
      start: 0,
      end: 0,
    }
    auto_bind(this)
  }

  shouldComponentUpdate(props, state) {
    return !props.date.isSame(this.props.date) ||
      props.color !== this.props.color ||
      exist(range(chunks_count_in_day), (chunk) => props.chunks[chunk] !== this.props.chunks[chunk]) ||
      state !== this.state
  }

  dragging_paint({start, end}) {
    if (start < end) {
      return !this.count(start + 1)
    } else if (start > end) {
      return !this.count(start - 1)
    } else {
      return this.count(start + 1) == this.count(start - 1) ? this.count(start) == 0 : this.count(start) > 0
    }
  }

  offset({start, end}) {
    let offset = 0
    if (start <= end) {
      offset = chunks_count_in_hour - 1
    }
    let count = this.count(start)
    if (!this.props.chunks[start * chunks_count_in_hour]) {
      count = chunks_count_in_hour - count
    }
    return (count + offset) % chunks_count_in_hour - offset
  }

  count(hour) {
    return range(hour * chunks_count_in_hour, (hour + 1) * chunks_count_in_hour)
    .filter((chunk) => this.props.chunks[chunk])
    .length
  }

  paint(hour) {
    return for_all(
      this.get_chunks(hour),
      property_getter('active'),
    )
  }

  get_chunks(hour) {
    return this.get_active_chunks().slice(hour * chunks_count_in_hour, (hour + 1) * chunks_count_in_hour)
  }

  drag_start(hour) {
    this.setState({start: hour, end: hour, is_dragging: true})
  }

  drag_hover(hour) {
    this.setState({end: hour})
  }

  drag_end() {
    this.setState({is_dragging: false})
    this.props.flip(this.props.date, this.get_dragging_chunks(), this.dragging_paint(this.state))
  }

  get_dragging_chunks() {
    let {start, end} = this.state
    let offset = this.offset(this.state)
    if (start > end) {
      start = this.state.end
      end = this.state.start
    }
    return range(start * chunks_count_in_hour + offset, (end + 1) * chunks_count_in_hour + offset)
  }

  get_active_chunks() {
    let active_chunks = range(chunks_count_in_day).map((chunk) => this.props.chunks[chunk])
    let paint = this.dragging_paint(this.state)
    if (this.state.is_dragging) {
      this.get_dragging_chunks().forEach((chunk) => active_chunks[chunk] = paint)
    }
    return active_chunks.map((active, index) => ({
      active,
      start: active && (index == 0 || !active_chunks[index - 1]),
      end: active && (index + 1 == chunks_count_in_day || !active_chunks[index + 1]),
    }))
  }

  on_hour_click(hour) {
    this.props.flip(this.props.date, range(hour * chunks_count_in_hour, (hour + 1) * chunks_count_in_hour), !this.paint(hour))
  }

  get_selectors() {
    return this.get_active_chunks().map(({start, end}, chunk) => {
      let select, offset
      if (start) {
        const chunk_base = chunks_count_in_hour * Math.floor(chunk / chunks_count_in_hour)
        select = (index) => {
          this.props.flip(this.props.date, range(chunk_base + index, chunk_base + chunks_count_in_hour), true)
          this.props.flip(this.props.date, range(chunk_base, chunk_base + index), false)
        }
        offset = -10
      } else if (end) {
        chunk = chunk + 1
        const chunk_base = chunks_count_in_hour * Math.floor(chunk / chunks_count_in_hour)
        select = (index) => {
          this.props.flip(this.props.date, range(chunk_base + index, chunk_base + chunks_count_in_hour), false)
          this.props.flip(this.props.date, range(chunk_base, chunk_base + index), true)
        }
        offset = +10
      }
      if (select) {
        return {position: chunk * 20 / chunks_count_in_hour + offset, chunk, select}
      }
    }).filter(Boolean)
  }

  render_selector(selector, index) {
    return <Selector key={index} {...selector} />
  }

  render_hour(hour) {
    let events_colors = array_from_set(filter_hash(
      map_hash(this.props.events, get_event_hours(day_id(this.props.date))),
      (active_hours) => active_hours[hour],
    ))
    return (
      <DragDropHour
        key={hour}
        hour={hour}
        drag_start={this.drag_start}
        drag_hover={this.drag_hover}
        drag_end={this.drag_end}
        onClick={this.on_hour_click}
        chunks={this.get_chunks(hour)}
        event_color={events_colors[0]}
        active_color={this.props.color}
        />
    )
  }

  render() {
    return (
      <div className={cx('time-pickers__day-hours')}>
        <div className={cx('time-pickers__day-hours-slider')}>
          {range(24).map(this.render_hour)}
        </div>
        <div className={cx('time-pickers__day-hours-selectors')}>
          {this.get_selectors().map(this.render_selector)}
        </div>
      </div>
    )
  }
}

class Hour extends Component {
  constructor(props) {
    super(props)
    auto_bind(this)
  }

  componentDidMount() {
    this.props.connectDragPreview(getEmptyImage(), {captureDraggingState: true})
  }

  onClick() {
    this.props.onClick(this.props.hour)
  }

  render_chunk({active, start, end}, index) {
    const {active_color} = this.props
    return (
      <div key={index} style={{backgroundColor: active ? active_color : null}} className={cx('time-pickers__hour-box-chunk')}>
        {start ? (
          <div style={{backgroundColor: active_color}} className={cx('time-pickers__hour-box-chunk-start')}>
            <div className={cx('time-pickers__hour-box-chunk-handle')}></div>
          </div>
        ) : null}
        {end ? (
          <div style={{backgroundColor: active_color}} className={cx('time-pickers__hour-box-chunk-end')}>
            <div className={cx('time-pickers__hour-box-chunk-handle')}></div>
          </div>
        ) : null}
      </div>
    )
  }

  render() {
    let {chunks, connectDropTarget, connectDragSource, event_color} = this.props
    return connectDropTarget(connectDragSource(
      <div className={cx('time-pickers__hour')}>
        <div
          style={{backgroundColor: event_color}}
          onClick={this.onClick}
          className={cx('time-pickers__hour-box')}
          >
          {chunks.map(this.render_chunk)}
        </div>
      </div>
    ))
  }
}

const DragDropHour = DropTarget(
  "time-pickers__hour",
  {
    hover(props, monitor, component) {
      let period = monitor.getItem()
      if (period) {
        if (period.end != props.hour) {
          props.drag_hover(props.hour)
          period.end = props.hour
        }
      }
    },
  },
  (connect, monitor) => ({
    connectDropTarget: connect.dropTarget(),
  })
)(DragSource(
  "time-pickers__hour",
  {
    beginDrag(props) {
      props.drag_start(props.hour)
      return {start: props.hour, end: props.hour}
    },
    endDrag(props) {
      props.drag_end()
    }
  },
  (connect, monitor) => ({
    connectDragSource: connect.dragSource(),
    connectDragPreview: connect.dragPreview(),
    isDragging: monitor.isDragging(),
  })
)(Hour))

class Selector extends Component {
  constructor(props) {
    super(props)
    auto_bind(this)
  }

  render_chunk(index) {
    const {chunk, select} = this.props
    return (
      <div
        key={index}
        className={cx(
          'time-pickers__selector-chunks-chunk',
          {'time-pickers__selector-chunks-chunk_selected': index == chunk % chunks_count_in_hour}
        )}
        onClick={() => select(index)}
        >
        {this.get_chunk(chunks_count_in_hour * Math.floor(chunk / chunks_count_in_hour) + index)}
      </div>
    )
  }

  get_chunk(chunk) {
    let minutes = chunk_length_in_minutes * (chunk % chunks_count_in_hour)
    return `${Math.floor(chunk / chunks_count_in_hour)}h${minutes < 10 ? 0 : ""}${minutes}`
  }

  render() {
    const {position, chunk} = this.props
    return (
      <div style={{left: position}} className={cx('time-pickers__selector')}>
        <div className={cx('time-pickers__selector-container')}>
          <Hover>
            {(hover) => hover ? (
              <div className={cx('time-pickers__selector-chunks')}>
                {range(chunks_count_in_hour).map(this.render_chunk)}
              </div>
            ) : (
              <div className={cx('time-pickers__selector-chunk')}>
                {this.get_chunk(chunk)}
              </div>
            )}
          </Hover>
        </div>
      </div>
    )
  }
}

TimePickers.defaultProps = {
  day_label: true,
  days: [],
  on_drag_and_drop: (date, start, end, paint) => {},
  full_day_color: '',
  drag_color: '',
  events: {},
}

export default TimePickers
