text: var api = {
    cache: {},
    http(verb, url, body) {
      var fullHeaders = {}
      fullHeaders["Content-Type"] = "application/json"
      fullHeaders["X-CSRFToken"] = getCookie("pretalx_csrftoken")
  
      let options = {
        method: verb || "GET",
        headers: fullHeaders,
        credentials: "include",
        body: body && JSON.stringify(body),
      }
      return window
        .fetch(url, options)
        .then(response => {
          if (response.status === 204) {
            return Promise.resolve()
          }
          return response.json().then(json => {
            if (!response.ok) {
              return Promise.reject({ response, json })
            }
            return Promise.resolve(json)
          })
        })
        .catch(error => {
          return Promise.reject(error)
        })
    },
    fetchTalks() {
      var url = [
        window.location.protocol,
        "//",
        window.location.host,
        window.location.pathname,
        "api/talks/",
        window.location.search,
      ].join("")
      return api.http("GET", url, null)
    },
    fetchRooms(eventSlug) {
      const url = [
        window.location.protocol,
        "//",
        window.location.host,
        "/api/events/",
        eventSlug,
        "/rooms",
      ].join("")
      return api.http("GET", url, null)
    },
    fetchAvailabilities(talkid, roomid, check_cache = true) {
      var url = [
        window.location.protocol,
        "//",
        window.location.host,
        window.location.pathname,
        "api/availabilities/",
        talkid,
        "/",
        roomid,
        "/",
        window.location.search,
      ].join("")
  
      if (check_cache && api.cache[url]) {
        return api.cache[url]
      } else {
        api.cache[url] = api.http("GET", url, null)
      }
  
      return api.cache[url]
    },
    saveTalk(talk) {
      var url = [
        window.location.protocol,
        "//",
        window.location.host,
        window.location.pathname,
        "api/talks/",
        talk.id ? (talk.id + "/") : "",
        window.location.search,
      ].join("")
      const action = talk.action || "PATCH"
      return api.http(action, url, {
        room: (talk.room && talk.room.id) ? talk.room.id : talk.room,
        start: talk.start,
        action: talk.action,
        duration: talk.duration,
        description: talk.description,
      })
    },
    deleteTalk(talk) {
      return api.saveTalk({id: talk.id, action: "DELETE"})
    },
    createTalk(talk) {
      talk.action = "POST"
      return api.saveTalk(talk)
    }
  }
  
  var dragController = {
    draggedTalk: null,
    modalTalk: null,
  
    event: null,
    roomColumn: null,
    dragPosX: null,
    dragPosY: null,
    startY: null,
    dragSource: null,
    isCreating: false,
  
    startDraggingTalk(talk, dragSource, dragPosX, dragPosY) {
      this.draggedTalk = JSON.parse(JSON.stringify(talk))
      this.dragPosX = dragPosX
      this.dragSource = dragSource
      this.dragPosY = dragPosY
      this.dragSource.classList.add("drag-source")
    },
    stopDragging() {
      if (this.roomColumn) {
        this.roomColumn.classList.remove("hover-active")
        this.dragSource.classList.remove("drag-source")
        this.draggedTalk = null
        this.event = null
        this.startY = null
      }
    },
    startModal(talk) {
      this.modalTalk = talk
      this.draggedTalk = null
      this.stopDragging()
    },
    switchToModal() {
      this.modalTalk = this.draggedTalk
      this.draggedTalk = null
      this.stopDragging()
    },
    closeModal() {
      this.modalTalk = null;
    }
  }
  
  function generateTimesteps(start, interval, intervalunit, end) {
    var steps = [],
      d = moment(start)
  
    while (d < end) {
      steps.push(moment(d))
      d.add(interval, intervalunit)
    }
  
    return steps
  }
  
  Vue.component("availability", {
    template: `
      <div class="room-availability" v-bind:style="style"></div>
    `,
    props: {
      availability: Object,
      start: Object,
    },
    computed: {
      duration() {
        if (this.availability.end) {
          return moment(this.availability.end).diff(
            this.availability.start,
            "minutes"
          )
        } else {
          return 60 * 24
        }
      },
      style() {
        var style = { height: this.duration + "px" }
        style.transform =
          "translatey(" +
          moment(this.availability.start).diff(this.start, "minutes") +
          "px)"
        return style
      },
    },
  })
  
  Vue.component("modal", {
    template: `
      <div id="schedule-modal" class="schedule-modal">
        <h2 v-if="talk.id">Edit break</h2>
        <h2 v-else>New break</h2>
        <form class="form-control">
          <div class="form-group row">
            <label class="col-md-3 col-form-label">Description</label>
            <div class="col-md-9"><div class="i18n-form-group">
              <input type="text" class="form-control" :title="locale" :lang="locale" v-model="talk.description[locale]" v-for="locale in locales" v-on:keyup.enter="saveTalk">
            </div></div>
          </div>
          <div class="form-group row">
            <label class="col-md-3 col-form-label">Duration</label>
            <div class="col-md-9"><div class="input-group">
              <input type="number" name="duration" value="30" min="5" class="form-control" placeholder="Duration" title="Duration in minutes" v-model="talk.duration" v-on:keyup.enter="saveTalk">
              <div class="input-group-append"><span class="input-group-append input-group-text">minutes</span></div>
            </div>
          </div></div>
          <div class="button-row">
            <div class="btn btn-outline-danger" @click="deleteTalk" v-if="talk.id">Delete</div>
            <div v-else></div>
            <div class="btn btn-success" @click="saveTalk">Save</div>
          </div>
        </form>
      </div>
    `,
    props: {
      talk: Object,
      locales: Array,
    },
    created () {
      if (!this.talk.description) {
        this.talk.description = {"en": ""}
      }
      if (!this.talk.duration && this.talk.start) {
        this.talk.duration = moment(this.talk.end).diff(this.talk.start, "minutes")
      }
    },
    methods: {
      deleteTalk () {
        api.deleteTalk(this.talk).then((resp) => {
          this.$emit("deleteTalk", this.talk)
          dragController.closeModal()
        })
      },
      saveTalk () {
        if (this.talk.id) {
          this.talk.end = moment(this.talk.start).add(this.talk.duration, "minutes")
          api.saveTalk(this.talk).then(resp => {
            dragController.closeModal()
            this.talk.duration = resp.duration
            this.talk.start = resp.start
            this.talk.end = resp.end
            this.$emit("saveTalk", this.talk)
          })
        } else {
          if (this.talk.duration) {
            api.createTalk(this.talk).then(resp => {
              dragController.closeModal()
              this.$emit("newTalk", resp)
            })
          }
        }
      },
    }
  })
  
  Vue.component("talk", {
    template: `
      <div class="talk-box" :class="[talk.state, {dragged: isDragged, warning: displayWarnings, break: isBreak}]" v-bind:style="style" @mousedown="onMouseDown"
           :title="title" :data-original-title="title" data-toggle="tooltip">
        <span v-if="displayWarnings" class="warning-sign"><i class="fa fa-warning"></i></span>
        <span v-if="(!isDragged || !this.talk.start) && talk.title">{{ talk.title }}</span>
        <span v-if="isBreak && !isDragged" class="description">{{ breakDescription }}</span>
        <span class="time" v-if="this.talk.start && this.isDragged && this.talk.id">
          <span>{{ humanStart }}</span>
        </span>
        <span class="time" v-else-if="this.isDragged">
          <span>{{ talk.duration }} minutes</span>
        </span>
      </div>
    `,
    props: {
      talk: Object,
      start: Object,
      isDragged: { type: Boolean, default: false },
    },
    computed: {
      title() {
        let title = this.start ? this.humanStart : ""
        title += " (" + this.talk.duration + " minutes)"
        if (this.displayWarnings) {
          title = title + "\n\n" + this.displayWarnings
        }
        if (this.talk.state === "accepted") {
          title = title + "\n Not confirmed yet!"
        }
        return title
      },
      breakDescription () {
        if (!this.talk.description) return ""
        if (this.talk.description.en) return this.talk.description.en
        return this.talk.description.values ? this.talk.description.values[0] : ""
      },
      style() {
        var style = { height: this.talk.duration + "px" }
        if (this.talk.track && this.talk.track.color) {
          style.borderColor = this.talk.track.color
        }
        style.transform =
          "translateY(" +
          moment(this.talk.start).diff(this.start, "minutes") +
          "px)"
        return style
      },
      humanStart() {
        return moment.tz(this.talk.start, app.timezone).format("HH:mm")
      },
      displayWarnings() {
        return this.talk.warnings
          ? this.talk.warnings.map(warning => warning.message).join("\n")
          : null
      },
      isBreak() {
        return !this.talk.submission_type
      },
    },
    methods: {
      onMouseDown(event) {
        if (event.buttons === 1) {
          var talkRect = this.$el.getBoundingClientRect()
          dragController.isCreating = false
          dragController.startDraggingTalk(
            this.talk,
            this.$el,
            event.clientX - talkRect.left,
            event.clientY - talkRect.top
          )
        }
      },
    },
  })
  
  Vue.component("timestep", {
    template: `
      <div class="timestep-box" :style="style" :class="[{midnight: isMidnight, thin: thin}]">
        <span v-if="!thin">
          {{ isMidnight ? timestep.format("MM-DD") : timestep.format("HH:mm") }}
        </span>
      </div>
    `,
    props: {
      timestep: Object,
      start: Object,
      thin: Boolean,
    },
    computed: {
      style() {
        if (!this.thin) return {}
        var style = { position: "absolute", top: 0 }
        style.transform =
          "translatey(" +
          moment(this.timestep).diff(this.start, "minutes") +
          "px)"
        return style
      },
      isMidnight() {
        return (
          (this.timestep.hour() === 0 && this.timestep.minute() === 0) &
          !this.thin
        )
      },
    },
  })
  
  Vue.component("room", {
    template: `
      <div class="room-column">
        <a v-bind:href="room.url" :title="displayName"><div class="room-header">{{ displayName }}</div></a>
        <div class="room-container" v-bind:style="style" :data-id="room.id" @mousedown="onMouseDown">
        <availability v-for="avail in availabilities" :availability="avail" :start="start" :key="avail.id"></availability>
        <talk ref="draggedTalk"
            v-if="dragController.draggedTalk && dragController.event && dragController.draggedTalk.room == this.room.id" :talk="dragController.draggedTalk" :key="dragController.draggedTalk.id" :is-dragged="true" :start="start">
        </talk>
        <talk v-for="talk in myTalks" :talk="talk" :start="start" :key="talk.id"></talk>
        <timestep v-for="timestep in midnights" :timestep="timestep" :start="start" :thin="true">
        </timestep>
        </div>
      </div>
    `,
    props: {
      talks: Array,
      room: Object,
      duration: Number,
      start: Object,
      end: Object,
    },
    asyncComputed: {
      availabilities() {
        if (dragController.draggedTalk && dragController.draggedTalk.id) {
          return api
            .fetchAvailabilities(dragController.draggedTalk.id, this.room.id)
            .then(result => result.results)
        } else {
          return this.room.availabilities
        }
      },
    },
    computed: {
      myTalks() {
        return (this.talks || []).filter(element => element.room === this.room.id)
      },
      midnights() {
        return generateTimesteps(this.start, 24, "h", this.end).slice(1)
      },
      displayName() {
        return Object.values(this.room.name)[0]
      },
      style() {
        return {
          height: this.duration + "px",
        }
      },
      dragController () { return dragController }
    },
    methods: {
      onMouseDown(event) {
        if (event.buttons === 1 && !dragController.draggedTalk) {
          var talkRect = this.$el.getBoundingClientRect()
          dragController.isCreating = true
          dragController.startDraggingTalk(
            {start: null, end: null, room: this.room, duration: null, description: null},
            this.$el,
            event.clientX - talkRect.left,
            0
          )
        }
      }
    }
  })
  
  var app = new Vue({
    el: "#fahrplan",
    template: `
      <div @mousemove="onMouseMove" @mouseup="onMouseUp">
        <div id="fahrplan" :class="showUnassigned ? 'narrow' : 'wide'">
          <modal ref="modalTalk" v-if="dragController.modalTalk && dragController.modalTalk.duration" :talk="dragController.modalTalk" v-on:deleteTalk="deleteTalk" v-on:saveTalk="saveTalk" :locales="locales" v-on:newTalk="newTalk"></modal>
          <div id="timeline" v-if="!loading">
            <div class="timeline-container">
              <timestep v-for="timestep in timesteps" :timestep="timestep" :start="start" :thin="false">
              </timestep>
            </div>
          </div>
          <div id="loading" v-if="loading">
              <i class="fa fa-spinner fa-pulse fa-4x fa-fw text-primary mb-4 mt-4"></i>
              <h3 class="mt-2 mb-4">Loading talks, please wait.</h3>
          </div>
          <div id="rooms" v-else>
            <div class="alert alert-danger room-column" v-if="rooms && rooms.length < 1">
              Please configure some rooms first.
            </div>
            <room v-for="room in rooms" :room="room" :talks="talks" :duration="duration" :start="start" :end="end" :key="room.id">
            </room>
          </div>
        </div>
        <div id="unassigned-group">
          <div class="talk-header" ref="roomHeader" @click="showUnassigned = !showUnassigned" :class="showUnassigned ? 'present': 'collapsed'">Unassigned Talks</div>
          <div id='unassigned-talks' v-if="showUnassigned">
            <div class="input-group">
              <div class="input-group-prepend input-group-text"><i class="fa fa-search"></i></div>
              <input type="text" class="form-control" placeholder="title track:foo type:baz" v-model="search">
            </div>
            <div id="unassigned-container" ref="unassigned">
                <talk v-for="talk in filteredTalks" v-if="!talk.room" :talk="talk" :key="talk.id"></talk>
            </div>
          </div>
        </div>
        <div id="new-talk" @click="dragController.startModal({})">
          +
        </div>
      </div>
    `,
    data() {
      return {
        talks: null,
        rooms: null,
        start: null,
        end: null,
        timezone: null,
        locales: null,
        loading: true,
        showUnassigned: true,
        search: "",
        dragController: dragController,
      }
    },
    created() {
      api.fetchRooms(this.eventSlug).then(result => {
        this.rooms = result.results
      })
      api
        .fetchTalks()
        .then(result => {
          this.talks = result.results.sort((a, b) => (a.title < b.title) ? -1 : 1)
          this.timezone = result.timezone
          this.start = moment.tz(result.start, this.timezone)
          this.end = moment.tz(result.end, this.timezone)
          this.locales = result.locales
        })
        .then(() => {
          this.loading = false
          $(function() {
            $('[data-toggle="tooltip"]').tooltip()
          })
        })
    },
    computed: {
      currentDay() {},
      duration() {
        return this.end ? this.end.diff(this.start, "minutes") : null
      },
      timesteps() {
        return generateTimesteps(this.start, 30, "m", this.end)
      },
      filteredTalks() {
        if (!this.talks) return []
        const searchTerms = this.search
            .split(" ")
            .filter(d => d.length)
            .map(d => d.toLowerCase().trim())
  
        if (!searchTerms.length) return this.talks
  
        return this.talks.filter(talk => {
          const speakers = talk.speakers || []
          const title = talk.title.toLowerCase()
          const submissionType = talk.submission_type ? talk.submission_type.toLowerCase() : ""
          const track = talk.track ? talk.track.name.toLowerCase() : ""
          return searchTerms.some(term => {
            if (track && term.startsWith("track:")) {
              if (track.includes(term.substring(6))) return true
            } else if (term.startsWith("type:")) {
              if (submissionType.includes(term.substring(5))) return true
            } else {
              if (title.includes(term) || speakers.some(speaker => speaker.name.toLowerCase().includes(term))) {
                return true
              }
            }
            return false
          })
        })
      },
      eventSlug() {
        const relevant = window.location.pathname.substring(12)
        return relevant.substring(0, relevant.indexOf("/"))
      },
    },
    methods: {
      deleteTalk (event) {
        // only removes talk from display
        const index = this.talks.indexOf(event);
        this.talks.splice(index, 1);
      },
      newTalk(talk) {
        this.talks.push(talk)
      },
      saveTalk (response) {
        const talk = this.talks.find((talk) => talk.id == response.id)
        Object.assign(talk, response)
      },
      onMouseMove(event) {
        if (dragController.draggedTalk) {
          dragController.event = event
          var newRoomColumn = document.elementFromPoint(
            event.clientX,
            event.clientY
          )
  
          while (
            !newRoomColumn.className.match(/room-container/) &&
            newRoomColumn.id !== "unassigned-container" &&
            newRoomColumn.parentElement
          ) {
            newRoomColumn = newRoomColumn.parentElement
          }
  
          if (newRoomColumn.dataset.id) {
            if (newRoomColumn && newRoomColumn !== dragController.roomColumn) {
              if (dragController.roomColumn)
                dragController.roomColumn.classList.remove("hover-active")
            }
            dragController.roomColumn = newRoomColumn
            dragController.draggedTalk.room = newRoomColumn.dataset.id
            dragController.roomColumn.classList.add("hover-active")
            if (dragController.roomColumn) {
              var colRect = dragController.roomColumn.getBoundingClientRect()
              var position = event.clientY - colRect.top - (dragController.dragPosY || 0)
              position -= position % 5
              const cursorTime = moment(this.start)
                .add(position, "minutes")
                .format()
              if (dragController.isCreating) {
                if (!dragController.draggedTalk.start) {
                  dragController.draggedTalk.start = cursorTime
                  dragController.startY = dragController.event.clientY
                }
                dragController.draggedTalk.end = cursorTime
                dragController.draggedTalk.duration = moment(cursorTime).diff(moment(dragController.draggedTalk.start), "minutes")
              } else {
                dragController.draggedTalk.start = cursorTime
              }
            }
          } else if (newRoomColumn.id === "unassigned-container") {
            if (newRoomColumn && newRoomColumn !== dragController.roomColumn) {
              if (dragController.roomColumn)
                dragController.roomColumn.classList.remove("hover-active")
            }
            dragController.roomColumn = newRoomColumn
            dragController.draggedTalk.room = null
            dragController.draggedTalk.start = null
            dragController.roomColumn.classList.add("hover-active")
          }
  
          if (event.clientY < 160) {
            if (event.clientY < 110) {
              window.scrollBy({
                top: -100,
                behavior: "smooth",
              })
            } else {
              window.scrollBy({
                top: -50,
                behavior: "smooth",
              })
            }
          } else if (event.clientY > window.innerHeight - 100) {
            if (event.clientY > window.innerHeight - 40) {
              window.scrollBy({
                top: 100,
                behavior: "smooth",
              })
            } else {
              window.scrollBy({
                top: 50,
                behavior: "smooth",
              })
            }
          }
        }
      },
      onMouseUp(event) {
        if (dragController.draggedTalk && !dragController.modalTalk) {
          if (dragController.event) {
            // got dragged
            if (!dragController.draggedTalk.submission_type && !dragController.draggedTalk.room) {
              api.deleteTalk(dragController.draggedTalk).then(response => {
                this.deleteTalk(dragController.draggedTalk)
              })
            } else {
              if (!dragController.draggedTalk.id) {
                dragController.switchToModal()
              } else {
                api.saveTalk(dragController.draggedTalk).then(response => { this.saveTalk(response) })
              }
            }
          } else {
            // this is a click
            const url = dragController.draggedTalk.url
            if (url) { // this is a talk, so we open a link
              dragController.stopDragging()
              window.open(url)
            } else {
              dragController.switchToModal()
            }
          }
        } else if (dragController.modalTalk) {
            let clickElement = document.elementFromPoint(
              event.clientX,
              event.clientY
            )
            while (
              clickElement.id !== "schedule-modal" &&
              clickElement.parentElement
            ) {
              clickElement = clickElement.parentElement
            }
          if (clickElement.id !== "schedule-modal") {
            dragController.closeModal();
          }
        }
        dragController.stopDragging()
      },
    },
  })
  