import CALENDARS from '../data/calendars.json'
import LOCATIONS from '../data/locations.json'

// https://stackoverflow.com/a/35970186
function invertColor(hex) {
  if (hex.indexOf('#') === 0) {
    hex = hex.slice(1)
  }
  // convert 3-digit hex to 6-digits.
  if (hex.length === 3) {
    hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]
  }
  if (hex.length !== 6) {
    throw new Error('Invalid HEX color.')
  }
  var r = parseInt(hex.slice(0, 2), 16),
    g = parseInt(hex.slice(2, 4), 16),
    b = parseInt(hex.slice(4, 6), 16)
  // https://stackoverflow.com/a/3943023/112731
  return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? '#000000' : '#FFFFFF'
}

const parseCalendars = () => {
  let calendars = {};

  for (const [cloud, cals] of Object.entries(CALENDARS.sources)) {
    for (const [id, attrs] of Object.entries(cals)) {
      calendars[id] = { cloud: cloud, ...attrs }
    }
  }

  return calendars;
}

const calendars = parseCalendars()

const calendarsByName = Object.fromEntries(
  Object.entries(calendars).map(([id, { name }]) => [name, id])
)

export const initialCalendars = Array.from(
  Object.entries(calendars).map(([_, { name, initial }]) => [name, initial ?? true])
)
  .filter(cal => cal[1])
  .map(cal => cal[0])

export const calendarTree = CALENDARS.tree

const dfs = (p, t, l) => {
  for (const [c, s] of Object.entries(t)) {
    l[c] = p === null ? [] : [p, ...l[p]]
    dfs(c, s, l)
  }
}

export const ancestors = (() => {
  let l = []
  dfs(null, calendarTree, l)
  return l
})()

export function getSubCalendars(name, tree = calendarTree) {
  let ret
  for (const [cal, subTree] of Object.entries(tree)) {
    if (cal === name) {
      ret = subTree
    } else {
      ret = ret || getSubCalendars(name, subTree)
    }
  }
  return ret
}

const calendarIds = Object.keys(calendars)

function mkCalendarUrl(id, { cloud }, extra = {}) {
  return (
    `/cal/${cloud}/${id}/?` +
    new URLSearchParams({
      ...extra,
      export: true,
      expand: true,
      accept: 'jcal'
    })
  )
}

function mkExportUrl(id, { cloud }) {
  return `/cal/${cloud}/${id}/?export`
}

function fetchCalendar(id, cal, extra = {}) {
  return fetch(mkCalendarUrl(id, cal, extra), { credentials: 'omit' })
    .then(resp => resp.json())
    .catch(err => console.error(err))
}

class Calendar {
  constructor(id, calendar) {
    const metadata = calendars[id]
    this.name = metadata.name
    this.short_name = metadata.short_name
    this.color = metadata.color || calendar[1][4][3]
    this.default_location = metadata.default_location
    this.events = calendar[2]
      .filter(item => item[0] === 'vevent')
      .map(item => this._parse_vevent(item[1]))
  }

  _parse_vevent(vevent) {
    const event = {}
    vevent.forEach(elt => {
      event[elt[0]] = elt[3]
    })
    return event
  }
}

function findLocationId(location) {
  const correctedLocation = LOCATIONS.nameMap[location] || location

  const result = Object.entries(LOCATIONS.rooms).find(([building, rooms]) =>
    rooms.includes(correctedLocation)
  )

  if (result === undefined) return undefined
  const [building, _] = result
  return `${building}-${correctedLocation}`
}

function fcEventFromjCalEvent(cal) {
  return function (evt) {
    const start = new Date(evt.dtstart)
    const end = new Date(evt.dtend)
    const fcEvent = {
      title: `${cal.short_name ?? cal.name} : ${evt.summary}`,
      start: evt.dtstart,
      end: evt.dtend,
      color: cal.color,
      textColor: invertColor(cal.color),
      duration: end - start // in ms
    }

    fcEvent.calendar = cal.name
    fcEvent.short_name = evt.summary
    fcEvent.description = evt.description
    fcEvent.location = evt.location || cal.default_location

    if (fcEvent.location) {
      fcEvent.resourceId = findLocationId(fcEvent.location)
    }

    if (evt.status) {
      fcEvent.status = evt.status
      fcEvent.classNames = [`st-${evt.status.toLowerCase()}`]
    }

    if (evt.rrule) {
      const { freq, byday, interval } = evt.rrule
      fcEvent.rrule = {
        freq,
        byweekday: byday,
        dtstart: evt.dtstart
      }

      if (interval) {
        fcEvent.rrule.interval = interval
      }
    }

    return fcEvent
  }
}

function mkEventsFromCalendar(id, cal) {
  return fetchCalendar(id, cal).then(calendar => {
    if (calendar[0] !== 'vcalendar') return
    const cal = new Calendar(id, calendar)
    return cal.events.map(fcEventFromjCalEvent(cal))
  })
}

export function mkSource(name) {
  const calendarId = calendarsByName[name]
  if (!calendarId) return null
  const calendar = calendars[calendarId]

  return {
    id: name,
    ...(calendar?.meta || {}),
    success: calendarData => {
      if (calendarData[0] !== 'vcalendar') return
      const cal = new Calendar(calendarId, calendarData)
      return cal.events.map(fcEventFromjCalEvent(cal))
    },
    failure: error => {
      console.error(`Fatal error during event source fetching of '${name}': ${error}`)
    },
    events: (info, successCallback, failureCallback) => {
      const { start, end } = info
      fetchCalendar(calendarId, calendar, {
        start: start.valueOf() / 1000,
        end: end.valueOf() / 1000
      }).then(successCallback, failureCallback)
    },
    export_url: mkExportUrl(calendarId, calendar)
  }
}
