import React, {Component} from 'react'
import ReactList from 'react-list'
import {range} from '../common/utilities'
import {color} from '../common/constants'
import moment from '../common/moment'
import {event_intersects} from '../common/calendar'

const column_width = 40
const diameter = 24
const min_height = diameter * 24
const number_of_days = 365

let tolerant_equal = (previous, next) => {
  if (previous === next) {
    return true
  } else {
    return false
  }
}

let props_selector = ((selectors, builder) => {
  let previous_values = selectors.map(() => null)
  let result
  return (props) => {
    let next_values = selectors.map((selector) => selector(props))
    if (next_values.reduce((unchanged, value, index) => unchanged && tolerant_equal(value, previous_values[index]), true)) {
      return result
    } else {
      return builder(...next_values)
    }
  }
})

let get_all_days_events = (props) => props.day_events
let get_all_hours_events = (props) => props.hour_events
let get_day = (day_number) => moment().startOf('day').add(day_number, 'days')
let make_get_day_events = (day_number) => props_selector(
  [get_all_days_events, get_all_hours_events],
  (all_days_events, all_hours_events) => {
    let day = get_day(day_number)
    let events = [all_days_events, all_hours_events].map((all_events) => {
      return Object.keys(all_events).reduce((events, hue) => ({
        ...events,
        [hue]: all_events[hue].filter((e) => event_intersects(e, day, 1, 'day'))
      }), {})
    })
    return {day_events: events[0], hour_events: events[1]}
  }
)

let make_get_day_hours = (day_number) => props_selector(
  [make_get_day_events(day_number)],
  (events) => {
    return range(26).map((hour_id) => {
      let hour = get_day(day_number).add(hour_id - 1, 'hours')
      let find_corresponding_event = (events) => events.reduce((corresponding, e) => event_intersects(e, hour, 1, 'hour') ? e : corresponding, null)
      return Object.keys(events.hour_events).reduce((result, hue) => {
        let event = find_corresponding_event(events.hour_events[hue])
        if (event) {
          return {...result, event, hue}
        } else {
          return result
        }
      }, {hour})
    }).map(({hour, event, hue}, index, hours) => {
      if (index > 0 && index < 26) {
        if (event) {
          let start = hue == hours[index - 1].hue
          let end = hue == hours[index + 1].hue
          let category
          if (start) {
            if (end) {
              category = 'middle'
            } else {
              category = 'start'
            }
          } else {
            if (end) {
              category = 'end'
            } else {
              category = 'one'
            }
          }
          return {category, hue, event, hour}
        } else {
          return {category: 'none', hue: color('primary', 'light'), hour}
        }
      } else {
        return null
      }
    }).slice(1, 25)
  }
)

let make_get_day_event = (day_number) => props_selector(
  [make_get_day_events(day_number)],
  (events) => {
    return Object.keys(events.day_events).reduce(
      (result, hue) => events.day_events[hue].length > 0 ? {hue, day_event: events.day_events[hue][0]} : result,
      Object.keys(events.hour_events).reduce(
        (result, hue) => events.hour_events[hue].length > 0 ? {hue, day_event: events.hour_events[hue][0]} : result,
        {hue: null, day_event: false}
      )
    )
  }
)

class Week extends Component {
  constructor(props) {
    super(props)
    this.state = {
      width: 100,
      height: min_height,
    }
    this.day_event_getters = range(number_of_days).map((day_number) => make_get_day_event(day_number))
    this.day_hours_getters = range(number_of_days).map((day_number) => make_get_day_hours(day_number))
    this.update_dimensions = this.update_dimensions.bind(this)
    this.render_day_header = this.render_day_header.bind(this)
    this.render_day = this.render_day.bind(this)
    this.on_days_scroll = this.on_days_scroll.bind(this)
    this.scroll_to_day = this.scroll_to_day.bind(this)
  }

  componentDidMount() {
    this.update_dimensions()
    $(window).resize(this.update_dimensions)
    $(this.days_container).scroll(() => $(this.day_headers_container).scrollLeft($(this.days_container).scrollLeft()))
    $(this.day_headers_container).scroll(() => $(this.days_container).scrollLeft($(this.day_headers_container).scrollLeft()))
  }

  componentWillUnmount() {
    $(window).off("resize", this.update_dimensions)
  }

  scroll_to_day(day) {
    $(this.days_container).scrollLeft(day.diff(get_day(0), 'days') * column_width)
  }

  render_day_header(day_number, key) {
    let {hue, day_event} = this.day_event_getters[day_number](this.props)
    let day = get_day(day_number)
    return (
      <DayHeader
        key={key}
        day={day}
        hue={day_event ? hue : null}
        on_click={() => this.props.on_day_click(day, day_event)}
        />
    )
  }

  render_day(day_number, key) {
    let {hue, day_event} = this.day_event_getters[day_number](this.props)
    return (
      <Day
        key={key}
        background_hue={day_event.full_days ? hue : null}
        hours={this.day_hours_getters[day_number](this.props)}
        on_click={day_event.full_days ? (() => {}) : this.props.on_hour_click}
        height={(this.state.height > min_height ? this.state.height : min_height) - 84}
        />
    )
  }

  update_dimensions() {
    let width = $(this.container).width()
    let height = $(this.container).height()
    if (width != this.state.width || height != this.state.height) {
      this.setState({width, height})
    }
  }

  render() {
    let start = moment().startOf('day')
    return (
      <div style={{flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'stretch'}} ref={(container) => this.container = container}>
        <div style={{marginLeft: 40, height: 84 + this.props.scrollbar_size, flexShrink: 0, overflowX: 'scroll', width: this.state.width - 40}} ref={(day_headers_container) => this.day_headers_container = day_headers_container}>
          <ReactList
            ref={(day_headers) => this.day_headers = day_headers}
            axis={'x'}
            length={number_of_days}
            itemRenderer={
              (column, key) => this.render_day_header(column, key)
            }
            type='uniform'
            />
        </div>
        <div style={{height: this.state.height - 84, display: 'flex', flexDirection: 'row', overflowY: 'auto', width: this.state.width}}>
          <div style={{width: 40, flexShrink: 0, display: 'flex', flexDirection: 'column', alignItems: 'stretch', height: (this.state.height > min_height ? this.state.height : min_height) - 84}}>
            {range(13).map((hour) => <DisplayedHour hour={2 * hour} key={hour} />)}
          </div>
          <div style={{height: (this.state.height > min_height ? this.state.height : min_height) - 84 + this.props.scrollbar_size, width: this.state.width - 40, overflowX: 'scroll', overflowY: 'hidden'}} ref={(days_container) => this.days_container = days_container}>
            <ReactList
              axis={'x'}
              length={number_of_days}
              itemRenderer={
                (column, key) => this.render_day(column, key)
              }
              type='uniform'
              />
          </div>
        </div>
      </div>
    )
  }

  on_days_scroll() {
    this.day_headers.scrollTo($(this.days_container).scrollLeft() / column_width)
  }
}

Week.defaultProps = {on_day_click: () => {}, on_hour_click: () => {}, day_events: {}, hour_events: {}}

class DayHeader extends Component {
  render() {
    return (
      <div style={{width: column_width, display: 'flex', flexDirection: 'column', flexShrink: 0, alignItems: 'stretch', display: 'inline-block'}}>
        <div style={{height: column_width, flexShrink: 0, display: 'flex', justifyContent: 'center', alignItems: 'center'}}>
          <div onClick={this.props.on_click} style={{width: diameter, height: diameter, borderRadius: diameter/2, backgroundColor: this.props.hue ? this.props.hue : 'white', color: this.props.hue ? 'white' : color('primary'), textAlign: 'center', verticalAlign: 'middle', display: 'flex', justifyContent: 'center', flexDirection: 'column'}}>
            <span style={{fontSize: 12}}>{this.props.day.date()}</span>
          </div>
        </div>
        <div style={{height: 14, flexShrink: 0, display: 'flex', justifyContent: 'center', alignItems: 'center'}}>
          {this.props.day.date() == 1 ? (
            <span style={{color: color('primary'), fontSize: 10}}>
              {this.props.day.format("MMM")}
            </span>
          ) : null}
        </div>
        <div style={{height: 30, flexShrink: 0, display: 'flex', justifyContent: 'center', alignItems: 'center'}}>
          <span style={{color: color('primary', 'light'), fontWeight: 900}}>
            {this.props.day.format("dd")[0]}
          </span>
        </div>
      </div>
    )
  }
}

class Day extends Component {
  render() {
    return (
      <div style={{width: column_width, display: 'inline-block', height: this.props.height}}>
        <div style={{width: column_width, height: this.props.height, display: 'flex', flexDirection: 'column', alignItems: 'stretch', overflow: 'hidden'}}>
          {this.props.hours.map(({hue: hour_hue, category, event, hour}, index) =>
            <Hour
              key={index}
              hue={this.props.background_hue ? this.props.background_hue : hour_hue}
              category={this.props.background_hue ? 'middle' : category}
              on_click={() => this.props.on_click(hour, event)}
              />
          )}
        </div>
      </div>
    )
  }
}

const DisplayedHour = (props) => {
  switch (props.hour) {
    case 0:
      return (
        <div style={{flex: 0.6, display: 'flex', justifyContent: 'center', alignItems: 'center'}}>
        </div>
      )
    case 24:
      return (
        <div style={{flex: 0.4, display: 'flex', justifyContent: 'center', alignItems: 'center'}}>
        </div>
      )
    default:
      return (
        <div style={{flex: 1, display: 'flex', justifyContent: 'center', alignItems: 'center', backgroundColor: props.hour % 2 ? color('important') : 'white'}}>
          <span style={{textAlign: 'center'}}>
            {props.hour}h
          </span>
        </div>
      )
  }
}

class Hour extends Component {
  render() {
    return (
      <div style={{flex: 1, position: 'relative', display: 'flex', justifyContent: 'center', alignItems: 'center'}} onClick={this.props.on_click}>
        <Sticker {...this.props} />
        <div style={{backgroundColor: this.props.category == "none" ? color('primary', 'light') : 'white', width: 2, height: 2, borderRadius: 1, zIndex: 1}} />
      </div>
    )
  }
}

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 'start':
        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 'end':
        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 styles = {
  sticker_container: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: (column_width - diameter)/2,
    right: (column_width - diameter)/2,
    display: 'flex',
    alignItems: 'stretch',
    flexDirection: 'column',
    justifyContent: 'center',
  },
  middle_sticker: {
    flex: 1,
  },
  filler: {
    display: 'flex',
    position: 'absolute',
    left: 0,
    right: 0,
    top: 0,
    bottom: 0,
    flexDirection: 'column',
    alignItems: 'stretch',
  },
  half_sticker: {
    flex: 1,
  },
  one_sticker: {
    borderRadius: diameter/2,
    height: diameter,
    width: diameter,
  },
}

export default Week
