/* globals Math */

import Service from 'ember-service';
import moment from 'moment';
import computed from 'ember-computed';
import EmberObject from 'ember-object';
import injectService from 'ember-service/inject';
import { normalizeVideoId } from 'web-client/utilities/normalize-video-id';

const HORIZONTAL_LINE_MESSAGE = EmberObject.create({isHorizontalLine: true, grayed: true});
const BUFFER_LENGTH_IN_MILLISECONDS = 30 * 1000;
const BOTTOM_MESSAGE_BUFFER_SIZE = 100;
const NON_BOTTOM_MESSAGE_BUFFER_SIZE = 200;
const COLORS = [
    '#FF0000', '#0000FF', '#008000', '#B22222', '#FF7F50', '#9ACD32',
    '#FF4500', '#2E8B57', '#DAA520', '#D2691E', '#5F9EA0', '#1E90FF',
    '#FF69B4', '#8A2BE2', '#00FF7F'
];

export default Service.extend({
  store: injectService(),
  rechatModerationPermission: injectService('rechat-moderation-permission'),

  messages: [],
  loadedMessages: [],
  lastTickTime: 0,
  lastLoadTime: 0,
  colorSettings: EmberObject.create({}),
  stuckToBottom: true,

  timeTravelingForward: false,
  timeTravelingBackward: false,
  timeTraveling: computed.or('timeTravelingForward', 'timeTravelingBackward'),
  canModerate: false,
  notFound: false,

  pushMessage(msgObject) {
    if (msgObject.get('color') === null) {
      let colorSettings = this.get('colorSettings');
      let nick = msgObject.get('from');

      if (colorSettings.get(nick) === undefined) {
        let color = COLORS[Math.floor(Math.random() * COLORS.length)];
        colorSettings.set(nick, color);
      }
      msgObject.set('color', colorSettings.get(nick));
    }

    let messages = this.get('messages');
    messages.pushObject(msgObject);

    let messageLimit = this.get('stuckToBottom') ? BOTTOM_MESSAGE_BUFFER_SIZE : NON_BOTTOM_MESSAGE_BUFFER_SIZE;
    let overflowCount = messages.length - messageLimit;
    messages.removeAt(0, overflowCount);
  },

  buildTickRequestObject(time) {
    let vodCreatedAtUnixMilliseconds = moment.utc(this.get('video.recordedAt')).valueOf();
    let absoluteTime = Math.floor(vodCreatedAtUnixMilliseconds + time);
    let relativeTime = absoluteTime - vodCreatedAtUnixMilliseconds;

    return {
      loadedMessages: this.get('loadedMessages'),
      lastTickTime: this.get('lastTickTime'),
      vodId: this.get('video.id'),
      lastLoadTime: this.get('lastLoadTime'),
      vodCreatedAtUnixMilliseconds,
      absoluteTime,
      relativeTime,
      // We want to force you to request chunks on vodCreatedAt + (30second * N) where N is an integer
      // So we look at the relative time modulo 30 seconds and subtract that from the absolute time
      // And thus get the time of the chunk we should request -- the closest previous time that is a product of 30 seconds from the 0s relative timestamp
      chunkRequestTime: absoluteTime - (relativeTime % BUFFER_LENGTH_IN_MILLISECONDS)
    };
  },

  requestChunk(request) {
    // We want to reload the message buffer if you are past the old buffer
    // We should also never request messages if the rechat 404s for that video

    if ((request.lastLoadTime < request.absoluteTime) && !this.get('notFound')) {
      this.set('lastLoadTime', (request.chunkRequestTime + BUFFER_LENGTH_IN_MILLISECONDS));
      return this.get('store').query('rechat-message', { start: Math.floor(request.chunkRequestTime / 1000), video_id: `v${normalizeVideoId(request.vodId)}` }).then(messages => {
        request.loadedMessages.addObjects(messages.toArray());
        this.setProperties({'timeTravelingForward': false, 'timeTravelingBackward': false});
      }, error => {
        if (error.errors[0].status === 404) {
          this.setProperties({
            'timeTravelingForward': false,
            'timeTravelingBackward': false,
            'notFound': true
          });
        }
      });
    }
  },

  pushCachedMessages(request) {
    let pushableMessages = request.loadedMessages.filter(item => {
      let timestamp = item.get('timestamp');
      let isTimePastTimestamp = timestamp <= request.absoluteTime;
      let isTimestampBeforeThisInterval = timestamp > request.lastTickTime;
      let isTimeInBuffer = request.absoluteTime <= request.lastLoadTime + BUFFER_LENGTH_IN_MILLISECONDS;

      // Is it time to push the message?
      // Is the timestamp between the current time and the last tick time?
      // Have we seeked past our current buffer?
      return (isTimePastTimestamp && isTimestampBeforeThisInterval && isTimeInBuffer);
    });

    pushableMessages.forEach(message => {
      // In the case that we reload a message from the store that is dirty, clean it
      // E.g a user seeks back to a message that we displayed, don't show it grayed out
      if (message.get('hasDirtyAttributes')) {
        message.rollbackAttributes();
      }
      this.pushMessage(message);
    });
  },

  tick(time) {
    if (!this.get('video')) {
      return;
    }

    let request = this.buildTickRequestObject(time);
    // The order of player events when you seek is: timeupdate -> seeking -> timeupdate -> seeked.
    // The only way for us to know whether you seeked forward or backwards is to check to see if the change between ticks
    // is positive or negative
    this.set('timeDirectionForward', request.absoluteTime > request.lastTickTime);

    this.requestChunk(request);
    this.pushCachedMessages(request);

    this.set('lastTickTime', request.absoluteTime);
  },

  seek(time) {
    let messages = this.get('messages');

    // Remove all messages before horizontal line and horizontal line if it exists
    if (messages.get('length') !== 0) {
      messages = messages.removeAt(0, messages.indexOf(messages.findBy('isHorizontalLine', true)) + 1);
    }

    messages.forEach(message => {
      // 'grayed' is an DS.attr. When we Destroy record we can no longer update that value
      // we want still want the message to gray out, however, so we set a differently named value here
      // This won't get in the way of the above message.rollback, though
      if (message.get('isDeleted')) {
        message.set('deletedGrayed', true);
      } else {
        message.set('grayed', true);
      }
    });

    // We do not want to put a horizontal line if `messages` is empty or if the last message is a horizontal line
    if (messages.get('length') !== 0 && messages.get('lastObject') !== HORIZONTAL_LINE_MESSAGE) {
      let horizontalLineMessage = EmberObject.create(HORIZONTAL_LINE_MESSAGE);
      let timestamp = moment.utc(time).format('HH:mm:ss');

      horizontalLineMessage.set('timestamp', timestamp);
      this.pushMessage(horizontalLineMessage);
    }

    if (this.get('timeDirectionForward')) {
      this.set('timeTravelingForward', true);
    } else {
      this.set('timeTravelingBackward', true);
    }

    this.set('lastTickTime', 0);
    this.set('lastLoadTime', 0);
    this.set('loadedMessages', []);
  },

  setVideo(video) {
    this.set('video', video);
    this.set('messages', []);
    this.set('loadedMessages', []);
    this.set('lastTickTime', 0);
    this.set('lastLoadTime', 0);
    this.set('notFound', false);
    this.set('canModerate', false);

    if (this.get('video.channel._id')) {
      this.get('rechatModerationPermission').queryPermission({
        id: this.get('video.channel._id'),
        messageId: 123
      }).then(moderationPermission => {
        this.set('canModerate', moderationPermission);
      });
    }
  },

  clearVideo() {
    this.setVideo(null);
  },

  deleteMessage(id) {
    let message = this.get('messages').findBy('id', id);
    message.set('deleted', true);
    message.destroyRecord();
  }
});
