import React, {Component} from 'react'
import classNames from 'classnames/bind'
import InfiniteList from './infinite_list'
import {DragSource, DropTarget} from 'react-dnd'
import {getEmptyImage} from 'react-dnd-html5-backend'
import {MdSubdirectoryArrowRight} from 'react-icons/lib/md'
import {color} from '../common/constants'
import {range, capitalize_first_letter, array_contains, make_memoized} from '../common/utilities'
import moment from '../common/moment'
import auto_bind from '../common/auto_bind'
import {event_intersects} from '../common/calendar'
import {tolerant_equal, tolerant_selector} from '../selectors/base'

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

let get_start_end = (start_id, end_id) => {
  let start_of_week = moment().startOf('week')
  let start = start_id, end = end_id
  if (start > end) {
    start = end
    end = start_id
  }
  return {
    start: moment(start_of_week).add(start - 1, 'days'),
    end: moment(start_of_week).add(end - 1, 'days').endOf('day'),
  }
}

let get_events = (props, state) => {
  if (state.is_dragging && props.drag_color) {
    if (state.paint) {
      return {...props.events, [props.drag_color]: [...(props.events[props.drag_color] || []), {
        id: 'temp',
        ...get_start_end(state.start, state.end),
        full_days: true
      }]}
    } else {
      let other_events = {...props.events}
      if (other_events[props.drag_color]) {
        delete other_events[props.drag_color]
      }
      return {...other_events, [""]: [{
        id: 'temp',
        ...get_start_end(state.start, state.end),
        full_days: true,
      }], [props.drag_color]: props.events[props.drag_color] || []}
    }
  } else {
    return props.events
  }
}
let make_get_week_events = make_memoized((week_number) => tolerant_selector(
  [get_events],
  (events) => {
    let week = moment().startOf('week').add(week_number, 'weeks')
    let hues = Object.keys(events)
    return hues.reduce((other_events, hue) => ({
      ...other_events,
      [hue]: events[hue].filter((e) => event_intersects(e, moment(week).subtract(1, 'day'), 9, 'days'))
    }), {})
  }
))

let make_get_week_days = make_memoized((week_number) => tolerant_selector(
  [make_get_week_events(week_number)],
  (events) => {
    let week = moment().startOf('week').add(week_number, 'weeks')
    let days = range(9).map((day) => {
      let date = moment(week).add(day - 1, 'days')
      let hued_event = Object.keys(events).reduce((hued_event, hue) => {
        if (hued_event) {
          return hued_event
        } else {
          return events[hue].reduce((hued_event, e) => {
            if (hued_event) {
              return hued_event
            } else {
              if (event_intersects(e, date, 1, 'day')) {
                return {event: e, hue}
              } else {
                return null
              }
            }
          }, null)
        }
      }, null)
      return {date, ...hued_event}
    })
    const days_event_are_continuous = (i) => days[i].hue == days[i+1].hue
    for (let i = 0; i < 7; i ++) {
      let category
      if (days[i+1].event) {
        if (days_event_are_continuous(i)) {
          if (days_event_are_continuous(i+1)) {
            category = "middle"
          } else {
            category = "end"
          }
        } else {
          if (days_event_are_continuous(i+1)) {
            category = "start"
          } else {
            category = "one"
          }
        }
      } else {
        category = "none"
      }
      days[i+1].category = category
    }
    delete days[0]
    delete days[8]

    return days
  }
))

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

  componentDidMount() {
    this.update_month(moment())
  }

  scroll_to_week(week) {
    this.weeks.scroll_to_week(week)
  }

  render() {
    return (
      <div className={cx('input-calendar')} style={{display: "flex", flexDirection: 'column', alignItems: 'stretch', flex: 1}}>
        <div style={{flexShrink: 0, height: 30, display: "flex", flexDirection: "column", justifyContent: 'center', alignItems: "center"}}>
          <div ref={(month_label) => this.month_label = month_label} style={{textAlign: 'center', color: color('primary', 'light'), fontWeight: 900}}>
          </div>
        </div>
        <div style={{marginLeft: this.props.hide_navigation ? 0 : 50, flexShrink: 0, height: 30, display: "flex", flexDirection: "row", alignItems: "center", justifyContent: "space-around", borderBottomStyle: "solid", borderBottomWidth: 2, borderBottomColor: color('primary', 'light')}}>
          {["L", "M", "M", "J", "V", "S", "D"].map((day, index) => <WeekDay day={day} key={index} />)}
        </div>
        <div style={{flex: 1, cursor: this.props.drag_color ? 'ew-resize' : 'initial', display: 'flex', alignItems: 'stretch'}}>
          <Weeks
            ref={(weeks) => this.weeks = weeks}
            update_month={this.update_month}
            events={this.props.events}
            on_click={this.props.on_click}
            drag_color={this.props.drag_color}
            on_drag_and_drop={this.props.on_drag_and_drop}
            on_navigate_to_week={this.props.on_navigate_to_week}
            hide_navigation={this.props.hide_navigation}
            />
        </div>
      </div>
    )
  }

  update_month(month) {
    $(this.month_label).text(capitalize_first_letter(month.isSame(moment(), 'year') ? month.format("MMMM") : month.format("MMMM YYYY")))
  }
}

class Weeks extends Component {
  constructor(props) {
    super(props)
    this.state = {
      is_dragging: false,
      start: 0,
      end: 0,
      paint: true,
    }
    auto_bind(this)
    this.current_month = moment().startOf('month')
  }

  is_week_loaded(week) {
    return week < this.state.week_count - this.state.week_offset
  }

  on_week_click(date, event) {
    this.props.on_click(date, event)
  }

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

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

  drag_end(day) {
    this.setState({is_dragging: false})
    let {start, end} = get_start_end(this.state.start, this.state.end)
    this.props.on_drag_and_drop(start, end, this.state.paint)
  }

  scroll_to_week(week) {
    this.list.scroll_to_item(week)
  }

  handle_scroll(scrollTop) {
    let month = moment().startOf('week').add(scrollTop / row_height, 'weeks')
    if (!month.isSame(this.current_month, 'month')) {
      this.current_month = month
      this.props.update_month(month)
    }
  }

  render_week(index) {
    return (
      <Week
        key={index}
        days={make_get_week_days(index)(this.props, this.state)}
        on_click={this.on_week_click}
        week={index}
        drag_start={this.drag_start}
        drag_hover={this.drag_hover}
        drag_end={this.drag_end}
        on_navigate_to_week={this.props.on_navigate_to_week}
        hide_navigation={this.props.hide_navigation}
        />
    )
  }

  render() {
    return (
      <InfiniteList
        item_height={row_height}
        handle_scroll={this.handle_scroll}
        ref={(list) => this.list = list}
        >
        {this.render_week}
      </InfiniteList>
    )
  }
}

class WeekDay extends Component {
  render() {
    return (
      <span style={{color: color('primary', 'light'), fontWeight: 900}}>
        {this.props.day}
      </span>
    )
  }
}

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

  on_navigate_to_week() {
    this.props.on_navigate_to_week(this.props.week)
  }

  render() {
    return (
      <div className={cx('input-calendar__week')} style={{height: 50, width: this.props.width, display: 'flex', flexDirection: 'row', alignItems: 'stretch', justifyContent: 'space-around'}}>
        {this.props.hide_navigation ? null : (
          <div onClick={this.on_navigate_to_week} className={cx('input-calendar__week-link')}>
            <MdSubdirectoryArrowRight />
          </div>
        )}
        {this.props.days.map((day, index) => (
          <DragDropDay
            drag_start={this.props.drag_start}
            drag_hover={this.props.drag_hover}
            drag_end={this.props.drag_end}
            day={this.props.week * 7 + index}
            event={day.event}
            hue={day.hue}
            category={day.category}
            on_click={this.props.on_click}
            date={day.date}
            key={index}
            />
        ))}
      </div>
    )
  }
}

class Day extends Component {
  shouldComponentUpdate(props) {
    return props.day !== this.props.day ||
      props.event !== this.props.event ||
      props.hue !== this.props.hue ||
      props.category !== this.props.category ||
      !props.date.isSame(this.props.date)
  }

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

  render() {
    let hue
    switch (this.props.category) {
      case "none":
        hue = color('primary')
        break
      default:
        hue = this.props.hue ? 'white' : color('primary')
    }
    return this.props.connectDropTarget(this.props.connectDragSource(
      <div onClick={() => this.props.on_click(this.props.date, this.props.event)} style={{flex: 1, position: 'relative', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', backgroundColor: this.props.date.month() % 2 == 0 ? 'white' : color('black', 'pale')}}>
        <Sticker category={this.props.category} hue={this.props.hue} />
        <div style={{color: hue, zIndex: 1}}>
          {this.props.date.format("D")}
        </div>
        {this.props.date.date() == 1 ? (
          <div style={{color: this.props.hue ? this.props.hue : hue, zIndex: 1, fontSize: 10, position: 'absolute', bottom: 1, left: 0, right: 0, textAlign: 'center'}}>
            {this.props.date.format("MMM")}
          </div>
        ) : null}
      </div>
    ))
  }
}

const DragDropDay = DropTarget(
  "generic-calendar__day",
  {
    hover(props, monitor, component) {
      let period = monitor.getItem()
      if (period) {
        if (period.end != props.day) {
          props.drag_hover(props.day)
          period.end = props.day
        }
      }
    },
  },
  (connect, monitor) => ({
    connectDropTarget: connect.dropTarget(),
  })
)(DragSource(
  "generic-calendar__day",
  {
    beginDrag(props) {
      props.drag_start(props.day, !props.event)
      return {start: props.day, end: props.day}
    },
    endDrag(props) {
      props.drag_end()
    }
  },
  (connect, monitor) => ({
    connectDragSource: connect.dragSource(),
    connectDragPreview: connect.dragPreview(),
    isDragging: monitor.isDragging(),
  })
)(Day))

class Sticker extends Component {
  render() {
    switch (this.props.category) {
      case 'one':
        return (
          <div style={styles.sticker_container}>
            <div style={{...styles.one_sticker, backgroundColor: this.props.hue}} />
          </div>
        )
      case 'middle':
        return (
          <div style={styles.sticker_container}>
            <div style={{...styles.middle_sticker, backgroundColor: this.props.hue}} />
          </div>
        )
      case 'end':
        return (
          <div style={styles.sticker_container}>
            <div style={{...styles.filler, justifyContent: 'flex-start'}}>
              <div style={{...styles.half_sticker, backgroundColor: this.props.hue}} />
              <div style={styles.half_sticker} />
            </div>
            <div style={{...styles.one_sticker, backgroundColor: this.props.hue}} />
          </div>
        )
      case 'start':
        return (
          <div style={styles.sticker_container}>
            <div style={{...styles.filler, justifyContent: 'flex-end'}}>
              <div style={styles.half_sticker} />
              <div style={{...styles.half_sticker, backgroundColor: this.props.hue}} />
            </div>
            <div style={{...styles.one_sticker, backgroundColor: this.props.hue}} />
          </div>
        )
      default:
        return <div />
    }
  }
}

const row_height = 50
const diameter = 24

const styles = {
  sticker_container: {
    position: 'absolute',
    left: 0,
    right: 0,
    top: (row_height - diameter)/2,
    bottom: (row_height - diameter)/2,
    display: 'flex',
    alignItems: 'stretch',
    flexDirection: 'row',
    justifyContent: 'center',
  },
  middle_sticker: {
    flex: 1,
  },
  filler: {
    display: 'flex',
    position: 'absolute',
    left: 0,
    right: 0,
    top: 0,
    bottom: 0,
    flexDirection: 'row',
    alignItems: 'stretch',
  },
  half_sticker: {
    flex: 1,
  },
  one_sticker: {
    borderRadius: diameter/2,
    height: diameter,
    width: diameter,
  },
}

Calendar.defaultProps = {
  events: {},
  on_click: () => {},
  drag_color: '',
  on_drag_and_drop: (start, end, paint) => {},
  hide_navigation: false,
}

export default Calendar
