import { NotificationLevels } from 'discourse/lib/notification-levels';
import computed from "ember-addons/ember-computed-decorators";
import { on } from "ember-addons/ember-computed-decorators";
import { defaultHomepage } from 'discourse/lib/utilities';
import PreloadStore from 'preload-store';

function isNew(topic) {
  return topic.last_read_post_number === null &&
        ((topic.notification_level !== 0 && !topic.notification_level) ||
        topic.notification_level >= NotificationLevels.TRACKING);
}

function isUnread(topic) {
  return topic.last_read_post_number !== null &&
         topic.last_read_post_number < topic.highest_post_number &&
         topic.notification_level >= NotificationLevels.TRACKING;
}

const TopicTrackingState = Discourse.Model.extend({
  messageCount: 0,

  @on("init")
  _setup() {
    this.unreadSequence = [];
    this.newSequence = [];
    this.states = {};
  },

  establishChannels() {
    const tracker = this;

    const process = data => {
      if (data.message_type === "delete") {
        tracker.removeTopic(data.topic_id);
        tracker.incrementMessageCount();
      }

      if (data.message_type === "new_topic" || data.message_type === "latest") {
        const muted_category_ids = Discourse.User.currentProp("muted_category_ids");
        if (_.include(muted_category_ids, data.payload.category_id)) {
          return;
        }
      }

      // fill parent_category_id we need it for counting new/unread
      if (data.payload && data.payload.category_id) {
        var category = Discourse.Category.findById(data.payload.category_id);

        if (category && category.parent_category_id) {
          data.payload.parent_category_id = category.parent_category_id;
        }
      }

      if (data.message_type === "latest"){
        tracker.notify(data);
      }

      if (data.message_type === "new_topic" || data.message_type === "unread" || data.message_type === "read") {
        tracker.notify(data);
        const old = tracker.states["t" + data.topic_id];

        // don't add tracking state for read stuff that was not tracked in first place
        if (old || data.message_type !== "read") {
          if (!_.isEqual(old, data.payload)) {
            tracker.states["t" + data.topic_id] = data.payload;
            tracker.incrementMessageCount();
          }
        }
      }
    };

    this.messageBus.subscribe("/new", process);
    this.messageBus.subscribe("/latest", process);
    if (this.currentUser) {
      this.messageBus.subscribe("/unread/" + this.currentUser.get('id'), process);
    }

    this.messageBus.subscribe("/delete", msg => {
      const old = tracker.states["t" + msg.topic_id];
      if (old) {
        old.deleted = true;
      }
      tracker.incrementMessageCount();
    });

    this.messageBus.subscribe("/recover", msg => {
      const old = tracker.states["t" + msg.topic_id];
      if (old) {
        delete old.deleted;
      }
      tracker.incrementMessageCount();
    });
  },

  updateSeen(topicId, highestSeen) {
    if (!topicId || !highestSeen) { return; }
    const state = this.states["t" + topicId];
    if (state && (!state.last_read_post_number || state.last_read_post_number < highestSeen)) {
      state.last_read_post_number = highestSeen;
      this.incrementMessageCount();
    }
  },

  notify(data) {
    if (!this.newIncoming) { return; }
    if (data.payload && data.payload.archetype === "private_message") { return; }

    const filter = this.get("filter");
    const filterCategory = this.get("filterCategory");
    const categoryId = data.payload && data.payload.category_id;

    if (filterCategory && filterCategory.get("id") !== categoryId) {
      const category = categoryId && Discourse.Category.findById(categoryId);
      if (!category || category.get("parentCategory.id") !== filterCategory.get('id')) {
        return;
      }
    }

    if (filter === defaultHomepage()) {
      const suppressed_from_homepage_category_ids = Discourse.Site.currentProp("suppressed_from_homepage_category_ids");
      if (_.include(suppressed_from_homepage_category_ids, data.payload.category_id)) {
        return;
      }
    }

    if ((filter === "all" || filter === "latest" || filter === "new") && data.message_type === "new_topic") {
      this.addIncoming(data.topic_id);
    }

    if ((filter === "all" || filter === "unread") && data.message_type === "unread") {
      const old = this.states["t" + data.topic_id];
      if(!old || old.highest_post_number === old.last_read_post_number) {
        this.addIncoming(data.topic_id);
      }
    }

    if (filter === "latest" && data.message_type === "latest") {
      this.addIncoming(data.topic_id);
    }

    this.set("incomingCount", this.newIncoming.length);
  },

  addIncoming(topicId) {
    if (this.newIncoming.indexOf(topicId) === -1) {
      this.newIncoming.push(topicId);
    }
  },

  resetTracking() {
    this.newIncoming = [];
    this.set("incomingCount", 0);
  },

  // track how many new topics came for this filter
  trackIncoming(filter) {
    this.newIncoming = [];
    const split = filter.split('/');

    if (split.length >= 4) {
      filter = split[split.length-1];
      // c/cat/subcat/l/latest
      var category = Discourse.Category.findSingleBySlug(split.splice(1,split.length - 3).join('/'));
      this.set("filterCategory", category);
    } else {
      this.set("filterCategory", null);
    }

    this.set("filter", filter);
    this.set("incomingCount", 0);
  },

  @computed("incomingCount")
  hasIncoming(incomingCount) {
    return incomingCount && incomingCount > 0;
  },

  removeTopic(topic_id) {
    delete this.states["t" + topic_id];
  },

  // If we have a cached topic list, we can update it from our tracking
  // information.
  updateTopics(topics) {
    if (Em.isEmpty(topics)) { return; }

    const states = this.states;
    topics.forEach(t => {
      const state = states['t' + t.get('id')];

      if (state) {
        const lastRead = t.get('last_read_post_number');
        if (lastRead !== state.last_read_post_number) {
          const postsCount = t.get('posts_count');
          let newPosts = postsCount - state.highest_post_number,
              unread = postsCount - state.last_read_post_number;

          if (newPosts < 0) { newPosts = 0; }
          if (!state.last_read_post_number) { unread = 0; }
          if (unread < 0) { unread = 0; }

          t.setProperties({
            highest_post_number: state.highest_post_number,
            last_read_post_number: state.last_read_post_number,
            new_posts: newPosts,
            unread: unread,
            unseen: !state.last_read_post_number
          });
        }
      }
    });
  },

  sync(list, filter) {
    const tracker = this,
          states = tracker.states;

    if (!list || !list.topics) { return; }

    // compensate for delayed "new" topics
    // client side we know they are not new, server side we think they are
    for (let i=list.topics.length-1; i>=0; i--) {
      const state = states["t"+ list.topics[i].id];
      if (state && state.last_read_post_number > 0) {
        if (filter === "new") {
          list.topics.splice(i, 1);
        } else {
          list.topics[i].set('unseen', false);
          list.topics[i].set('dont_sync', true);
        }
      }
    }

    list.topics.forEach(function(topic){
      const row = tracker.states["t" + topic.id] || {};
      row.topic_id = topic.id;
      row.notification_level = topic.notification_level;


      if (topic.unseen) {
        row.last_read_post_number = null;
      } else if (topic.unread || topic.new_posts) {
        row.last_read_post_number = topic.highest_post_number - ((topic.unread||0) + (topic.new_posts||0));
      } else {
        if (!topic.dont_sync) {
          delete tracker.states["t" + topic.id];
        }
        return;
      }

      row.highest_post_number = topic.highest_post_number;
      if (topic.category) {
        row.category_id = topic.category.id;
      }

      tracker.states["t" + topic.id] = row;
    });

    // Correct missing states, safeguard in case message bus is corrupt
    if ((filter === "new" || filter === "unread") && !list.more_topics_url) {

      const ids = {};
      list.topics.forEach(r => ids["t" + r.id] = true);

      _.each(tracker.states, (v, k) => {

        // we are good if we are on the list
        if (ids[k]) { return; }

        if (filter === "unread" && isUnread(v)) {
          // pretend read
          v.last_read_post_number = v.highest_post_number;
        }

        if (filter === "new" && isNew(v)) {
          // pretend not new
          v.last_read_post_number = 1;
        }
      });
    }

    this.incrementMessageCount();
  },

  incrementMessageCount() {
    this.set("messageCount", this.get("messageCount") + 1);
  },

  countNew(category_id) {
    return _.chain(this.states)
            .where(isNew)
            .where(topic =>
                    topic.archetype !== "private_message" &&
                    !topic.deleted && (
                    topic.category_id === category_id ||
                    topic.parent_category_id === category_id ||
                    !category_id)
                  )
            .value()
            .length;
  },

  resetNew() {
    Object.keys(this.states).forEach(id => {
      if (this.states[id].last_read_post_number === null) {
        delete this.states[id];
      }
    });
  },

  countUnread(category_id) {
    return _.chain(this.states)
            .where(isUnread)
            .where(topic =>
                  topic.archetype !== "private_message" &&
                  !topic.deleted && (
                  topic.category_id === category_id ||
                  topic.parent_category_id === category_id ||
                  !category_id)
                )
            .value()
            .length;
  },

  countCategory(category_id) {
    let sum = 0;
    _.each(this.states, function(topic){
      if (topic.category_id === category_id && !topic.deleted) {
        sum += (topic.last_read_post_number === null ||
                  topic.last_read_post_number < topic.highest_post_number) ? 1 : 0;
      }
    });
    return sum;
  },

  lookupCount(name, category) {
    if (name === "latest") {
      return this.lookupCount("new", category) +
             this.lookupCount("unread", category);
    }

    let categoryId = category ? Em.get(category, "id") : null;
    let categoryName = category ? Em.get(category, "name") : null;

    if (name === "new") {
      return this.countNew(categoryId);
    } else if (name === "unread") {
      return this.countUnread(categoryId);
    } else {
      categoryName = name.split("/")[1];
      if (categoryName) {
        return this.countCategory(categoryId);
      }
    }
  },

  loadStates(data) {
    const states = this.states;
    const idMap = Discourse.Category.idMap();

    // I am taking some shortcuts here to avoid 500 gets for
    // a large list
    if (data) {
      _.each(data,topic => {
        var category = idMap[topic.category_id];
        if (category && category.parent_category_id) {
          topic.parent_category_id = category.parent_category_id;
        }
        states["t" + topic.topic_id] = topic;
      });
    }
  }


});

export function startTracking(tracking) {
  const data = PreloadStore.get('topicTrackingStates');
  tracking.loadStates(data);
  tracking.initialStatesLength = data && data.length;
  tracking.establishChannels();
  PreloadStore.remove('topicTrackingStates');
}

export default TopicTrackingState;
