/* global $ */

import SimpleWebRTC from 'simplewebrtc'
import SocketIoConnection from 'simplewebrtc/socketioconnection'

import Active from '../Active'
import DataModel from '../DataModel'
import JIT from '../JIT'
import Util from '../Util'
import Views from '../Views'
import Visualize from '../Visualize'

import {
  JUNTO_UPDATED,
  INVITED_TO_CALL,
  INVITED_TO_JOIN,
  CALL_ACCEPTED,
  CALL_DENIED,
  INVITE_DENIED,
  CALL_IN_PROGRESS,
  CALL_STARTED,
  MAPPER_LIST_UPDATED,
  MAPPER_JOINED_CALL,
  MAPPER_LEFT_CALL,
  NEW_MAPPER,
  LOST_MAPPER,
  MESSAGE_CREATED,
  TOPIC_DRAGGED,
  TOPIC_CREATED,
  TOPIC_UPDATED,
  TOPIC_REMOVED,
  TOPIC_DELETED,
  SYNAPSE_CREATED,
  SYNAPSE_UPDATED,
  SYNAPSE_REMOVED,
  SYNAPSE_DELETED,
  PEER_COORDS_UPDATED,
  MAP_UPDATED
} from './events'

import {
  juntoUpdated,
  invitedToCall,
  invitedToJoin,
  callAccepted,
  callDenied,
  inviteDenied,
  callInProgress,
  callStarted,
  mapperListUpdated,
  mapperJoinedCall,
  mapperLeftCall,
  peerCoordsUpdated,
  newMapper,
  lostMapper,
  messageCreated,
  topicDragged,
  topicCreated,
  topicUpdated,
  topicRemoved,
  topicDeleted,
  synapseCreated,
  synapseUpdated,
  synapseRemoved,
  synapseDeleted,
  mapUpdated
} from './receivable'

import {
  joinMap,
  leaveMap,
  checkForCall,
  acceptCall,
  denyCall,
  denyInvite,
  inviteToJoin,
  inviteACall,
  joinCall,
  leaveCall,
  sendCoords,
  sendMapperInfo,
  createMessage,
  dragTopic,
  createTopic,
  updateTopic,
  removeTopic,
  deleteTopic,
  createSynapse,
  updateSynapse,
  removeSynapse,
  deleteSynapse,
  updateMap
} from './sendable'

let Realtime = {
  juntoState: { connectedPeople: {}, liveMaps: {} },
  videoId: 'video-wrapper',
  socket: null,
  webrtc: null,
  readyToCall: false,
  mappersOnMap: {},
  disconnected: false,
  chatOpen: false,
  soundId: null,
  broadcastingStatus: false,
  inConversation: false,
  localVideo: null,
  'junto_spinner_darkgrey.gif': '',
  init: function(serverData) {
    var self = Realtime

    self.addJuntoListeners()

    self.socket = new SocketIoConnection({
      url: serverData['REALTIME_SERVER'],
      socketio: {
        // don't poll forever if in development
        reconnectionAttempts: serverData.RAILS_ENV === 'development' ? 5 : Infinity
      }
    })
    self['junto_spinner_darkgrey.gif'] = serverData['junto_spinner_darkgrey.gif']

    self.socket.on('connect', function() {
      console.log('connected')
      subscribeToEvents(self, self.socket)

      if (!self.disconnected) {
        self.startActiveMap()
      } else self.disconnected = false
    })
    self.socket.on('disconnect', function() {
      self.disconnected = true
    })

    if (Active.Mapper) {
      self.webrtc = new SimpleWebRTC({
        connection: self.socket,
        localVideoEl: self.videoId,
        remoteVideosEl: '',
        debug: true,
        detectSpeakingEvents: false, // true,
        autoAdjustMic: false, // true,
        autoRequestMedia: false,
        localVideo: {
          autoplay: true,
          mirror: true,
          muted: true
        },
        media: {
          video: true,
          audio: true
        },
        nick: Active.Mapper.id
      })
      self.webrtc.webrtc.on('iceFailed', function(peer) {
        console.log('local ice failure', peer)
        // local ice failure
      })
      self.webrtc.webrtc.on('connectivityError', function(peer) {
        console.log('remote ice failure', peer)
        // remote ice failure
      })

      var $video = $('<video></video>').attr('id', self.videoId)
      self.localVideo = {
        $video: $video,
        view: new Views.VideoView($video[0], $('body'), 'me', true, {
          DOUBLE_CLICK_TOLERANCE: 200,
          avatar: Active.Mapper ? Active.Mapper.get('image') : ''
        })
      }

      self.room = new Views.Room({
        webrtc: self.webrtc,
        socket: self.socket,
        username: Active.Mapper ? Active.Mapper.get('name') : '',
        image: Active.Mapper ? Active.Mapper.get('image') : '',
        room: 'global',
        $video: self.localVideo.$video,
        myVideoView: self.localVideo.view,
        config: { DOUBLE_CLICK_TOLERANCE: 200 },
        soundUrls: [
          serverData['sounds/MM_sounds.mp3'],
          serverData['sounds/MM_sounds.ogg']
        ]
      })
      self.room.videoAdded(self.handleVideoAdded)

      if (!Active.Map) {
        self.room.chat.$container.hide()
      }
      $('body').prepend(self.room.chat.$container)
    } // if Active.Mapper
  },
  addJuntoListeners: function() {
    var self = Realtime

    $(document).on(Views.ChatView.events.openTray, function() {
      $('.main').addClass('compressed')
      self.chatOpen = true
      self.positionPeerIcons()
    })
    $(document).on(Views.ChatView.events.closeTray, function() {
      $('.main').removeClass('compressed')
      self.chatOpen = false
      self.positionPeerIcons()
    })
    $(document).on(Views.ChatView.events.videosOn, function() {
      $('#wrapper').removeClass('hideVideos')
    })
    $(document).on(Views.ChatView.events.videosOff, function() {
      $('#wrapper').addClass('hideVideos')
    })
    $(document).on(Views.ChatView.events.cursorsOn, function() {
      $('#wrapper').removeClass('hideCursors')
    })
    $(document).on(Views.ChatView.events.cursorsOff, function() {
      $('#wrapper').addClass('hideCursors')
    })
  },
  startActiveMap: function() {
    var self = Realtime
    if (Active.Map && Active.Mapper) {
      if (Active.Map.authorizeToEdit(Active.Mapper)) {
        self.turnOn()
        self.setupSocket()
        self.setupLocalSendables()
      }
      self.room.addMessages(new DataModel.MessageCollection(DataModel.Messages), true)
    }
  },
  endActiveMap: function() {
    var self = Realtime
    $(document).off('.map')
    // leave the appropriate rooms to leave
    if (self.inConversation) self.leaveCall()
    self.leaveMap()
    $('.collabCompass').remove()
    if (self.room) {
      self.room.leave()
      self.room.chat.$container.hide()
      self.room.chat.close()
    }
  },
  turnOn: function(notify) {
    var self = Realtime
    $('.collabCompass').show()
    self.room.chat.$container.show()
    self.room.room = 'map-' + Active.Map.id
    self.activeMapper = {
      id: Active.Mapper.id,
      name: Active.Mapper.get('name'),
      username: Active.Mapper.get('name'),
      image: Active.Mapper.get('image'),
      color: Util.getPastelColor(),
      self: true
    }
    self.localVideo.view.$container.find('.video-cutoff').css({
      border: '4px solid ' + self.activeMapper.color
    })
    self.room.chat.addParticipant(self.activeMapper)
  },
  setupSocket: function() {
    var self = Realtime
    self.checkForCall()
    self.joinMap()
  },
  setupLocalSendables: function() {
    var self = Realtime

    // local event listeners that trigger events
    var sendCoords = function(event) {
      var pixels = {
        x: event.pageX,
        y: event.pageY
      }
      var coords = Util.pixelsToCoords(Visualize.mGraph, pixels)
      self.sendCoords(coords)
    }
    $(document).on('mousemove.map', sendCoords)

    var zoom = function(event, e) {
      if (e) {
        var pixels = {
          x: e.pageX,
          y: e.pageY
        }
        var coords = Util.pixelsToCoords(Visualize.mGraph, pixels)
        self.sendCoords(coords)
      }
      self.positionPeerIcons()
    }
    $(document).on(JIT.events.zoom + '.map', zoom)

    $(document).on(JIT.events.pan + '.map', self.positionPeerIcons)

    var dragTopic = function(event, positions) {
      self.dragTopic(positions)
    }
    $(document).on(JIT.events.topicDrag + '.map', dragTopic)

    var createTopic = function(event, data) {
      self.createTopic(data)
    }
    $(document).on(JIT.events.newTopic + '.map', createTopic)

    var deleteTopic = function(event, data) {
      self.deleteTopic(data)
    }
    $(document).on(JIT.events.deleteTopic + '.map', deleteTopic)

    var removeTopic = function(event, data) {
      self.removeTopic(data)
    }
    $(document).on(JIT.events.removeTopic + '.map', removeTopic)

    var createSynapse = function(event, data) {
      self.createSynapse(data)
    }
    $(document).on(JIT.events.newSynapse + '.map', createSynapse)

    var deleteSynapse = function(event, data) {
      self.deleteSynapse(data)
    }
    $(document).on(JIT.events.deleteSynapse + '.map', deleteSynapse)

    var removeSynapse = function(event, data) {
      self.removeSynapse(data)
    }
    $(document).on(JIT.events.removeSynapse + '.map', removeSynapse)

    var createMessage = function(event, data) {
      self.createMessage(data)
    }
    $(document).on(Views.Room.events.newMessage + '.map', createMessage)
  },
  countOthersInConversation: function() {
    var self = Realtime
    var count = 0
    for (var key in self.mappersOnMap) {
      if (self.mappersOnMap[key].inConversation) count++
    }
    return count
  },
  handleVideoAdded: function(v, id) {
    var self = Realtime
    self.positionVideos()
    v.setParent($('#wrapper'))
    v.$container.find('.video-cutoff').css({
      border: '4px solid ' + self.mappersOnMap[id].color
    })
    $('#wrapper').append(v.$container)
  },
  positionVideos: function() {
    var self = Realtime
    var videoIds = Object.keys(self.room.videos)
    // var numOfVideos = videoIds.length
    // var numOfVideosToPosition = _.filter(videoIds, function(id) {
    //   return !self.room.videos[id].manuallyPositioned
    // }).length

    var screenHeight = $(document).height()
    var topExtraPadding = 20
    var topPadding = 30
    var leftPadding = 30
    var videoHeight = 150
    var videoWidth = 180
    var column = 0
    var row = 0
    var yFormula = function() {
      var y = topExtraPadding + (topPadding + videoHeight) * row + topPadding
      if (y + videoHeight > screenHeight) {
        row = 0
        column += 1
        y = yFormula()
      }
      row++
      return y
    }
    var xFormula = function() {
      var x = (leftPadding + videoWidth) * column + leftPadding
      return x
    }

    // do self first
    var myVideo = Realtime.localVideo.view
    if (!myVideo.manuallyPositioned) {
      myVideo.$container.css({
        top: yFormula() + 'px',
        left: xFormula() + 'px'
      })
    }
    videoIds.forEach(function(id) {
      var video = self.room.videos[id]
      if (!video.manuallyPositioned) {
        video.$container.css({
          top: yFormula() + 'px',
          left: xFormula() + 'px'
        })
      }
    })
  },
  callEnded: function() {
    var self = Realtime

    self.room.conversationEnding()
    self.room.leaveVideoOnly()
    self.inConversation = false
    self.localVideo.view.$container.hide().css({
      top: '72px',
      left: '30px'
    })
    self.localVideo.view.audioOn()
    self.localVideo.view.videoOn()
  },
  createCompass: function(name, id, image, color) {
    var str = '<img width="28" height="28" src="' + image + '" /><p>' + name + '</p>'
    str += '<div id="compassArrow' + id + '" class="compassArrow"></div>'
    $('#compass' + id).remove()
    $('<div/>', {
      id: 'compass' + id,
      class: 'collabCompass'
    }).html(str).appendTo('#wrapper')
    $('#compass' + id + ' img').css({
      'border': '2px solid ' + color
    })
    $('#compass' + id + ' p').css({
      'background-color': color
    })
  },
  positionPeerIcons: function() {
    var self = Realtime
    for (var key in self.mappersOnMap) {
      self.positionPeerIcon(key)
    }
  },
  positionPeerIcon: function(id) {
    var self = Realtime
    var mapper = self.mappersOnMap[id]

    var origPixels = Util.coordsToPixels(Visualize.mGraph, mapper.coords)
    var pixels = self.limitPixelsToScreen(origPixels)
    $('#compass' + id).css({
      left: pixels.x + 'px',
      top: pixels.y + 'px'
    })
    /* showing the arrow if the collaborator is off of the viewport screen */
    if (origPixels.x !== pixels.x || origPixels.y !== pixels.y) {
      var dy = origPixels.y - pixels.y // opposite
      var dx = origPixels.x - pixels.x // adjacent
      var angle = Math.atan2(dy, dx)

      $('#compassArrow' + id).show().css({
        transform: 'rotate(' + angle + 'rad)',
        '-webkit-transform': 'rotate(' + angle + 'rad)'
      })

      if (dx > 0) {
        $('#compass' + id).addClass('labelLeft')
      }
    } else {
      $('#compassArrow' + id).hide()
      $('#compass' + id).removeClass('labelLeft')
    }
  },
  limitPixelsToScreen: function(pixels) {
    var self = Realtime

    var boundary = self.chatOpen ? '#wrapper' : document
    var xLimit, yLimit
    var xMax = $(boundary).width()
    var yMax = $(boundary).height()
    var compassDiameter = 56
    var compassArrowSize = 24

    xLimit = Math.max(0 + compassArrowSize, pixels.x)
    xLimit = Math.min(xLimit, xMax - compassDiameter)
    yLimit = Math.max(0 + compassArrowSize, pixels.y)
    yLimit = Math.min(yLimit, yMax - compassDiameter)

    return {x: xLimit, y: yLimit}
  }
}

const sendables = [
  ['joinMap', joinMap],
  ['leaveMap', leaveMap],
  ['checkForCall', checkForCall],
  ['acceptCall', acceptCall],
  ['denyCall', denyCall],
  ['denyInvite', denyInvite],
  ['inviteToJoin', inviteToJoin],
  ['inviteACall', inviteACall],
  ['joinCall', joinCall],
  ['leaveCall', leaveCall],
  ['sendMapperInfo', sendMapperInfo],
  ['sendCoords', sendCoords],
  ['createMessage', createMessage],
  ['dragTopic', dragTopic],
  ['createTopic', createTopic],
  ['updateTopic', updateTopic],
  ['removeTopic', removeTopic],
  ['deleteTopic', deleteTopic],
  ['createSynapse', createSynapse],
  ['updateSynapse', updateSynapse],
  ['removeSynapse', removeSynapse],
  ['deleteSynapse', deleteSynapse],
  ['updateMap', updateMap]
]
sendables.forEach(sendable => {
  Realtime[sendable[0]] = sendable[1](Realtime)
})

const subscribeToEvents = (Realtime, socket) => {
  socket.on(JUNTO_UPDATED, juntoUpdated(Realtime))
  socket.on(INVITED_TO_CALL, invitedToCall(Realtime))
  socket.on(INVITED_TO_JOIN, invitedToJoin(Realtime))
  socket.on(CALL_ACCEPTED, callAccepted(Realtime))
  socket.on(CALL_DENIED, callDenied(Realtime))
  socket.on(INVITE_DENIED, inviteDenied(Realtime))
  socket.on(CALL_IN_PROGRESS, callInProgress(Realtime))
  socket.on(CALL_STARTED, callStarted(Realtime))
  socket.on(MAPPER_LIST_UPDATED, mapperListUpdated(Realtime))
  socket.on(MAPPER_JOINED_CALL, mapperJoinedCall(Realtime))
  socket.on(MAPPER_LEFT_CALL, mapperLeftCall(Realtime))
  socket.on(PEER_COORDS_UPDATED, peerCoordsUpdated(Realtime))
  socket.on(NEW_MAPPER, newMapper(Realtime))
  socket.on(LOST_MAPPER, lostMapper(Realtime))
  socket.on(MESSAGE_CREATED, messageCreated(Realtime))
  socket.on(TOPIC_DRAGGED, topicDragged(Realtime))
  socket.on(TOPIC_CREATED, topicCreated(Realtime))
  socket.on(TOPIC_UPDATED, topicUpdated(Realtime))
  socket.on(TOPIC_REMOVED, topicRemoved(Realtime))
  socket.on(TOPIC_DELETED, topicDeleted(Realtime))
  socket.on(SYNAPSE_CREATED, synapseCreated(Realtime))
  socket.on(SYNAPSE_UPDATED, synapseUpdated(Realtime))
  socket.on(SYNAPSE_REMOVED, synapseRemoved(Realtime))
  socket.on(SYNAPSE_DELETED, synapseDeleted(Realtime))
  socket.on(MAP_UPDATED, mapUpdated(Realtime))
}

export default Realtime
