import offsetCalculator from 'discourse/lib/offset-calculator';
import LockOn from 'discourse/lib/lock-on';
import { defaultHomepage } from 'discourse/lib/utilities';

const rewrites = [];
const TOPIC_REGEXP = /\/t\/([^\/]+)\/(\d+)\/?(\d+)?/;

// We can add links here that have server side responses but not client side.
const SERVER_SIDE_ONLY = [
  /^\/posts\/\d+\/raw/,
  /\.rss$/,
  /\.json/,
];

let _jumpScheduled = false;
export function jumpToElement(elementId) {
  if (_jumpScheduled || Ember.isEmpty(elementId)) { return; }

  const selector = `#${elementId}, a[name=${elementId}]`;
  _jumpScheduled = true;
  Ember.run.schedule('afterRender', function() {
    const lockon = new LockOn(selector, {
      finished() {
        _jumpScheduled = false;
      }
    });
    lockon.lock();
  });
}

const DiscourseURL = Ember.Object.extend({

  isJumpScheduled() {
    return _jumpScheduled;
  },

  // Jumps to a particular post in the stream
  jumpToPost(postNumber, opts) {
    opts = opts || {};
    const holderId = `#post_${postNumber}`;

    Em.run.schedule('afterRender', () => {
      let elementId;
      let holder;

      if (postNumber === 1 && !opts.anchor) {
        $(window).scrollTop(0);
        return;
      }

      if (opts.anchor) {
        elementId = opts.anchor;
        holder = $(elementId);
      }

      if (!holder || holder.length === 0) {
        elementId = holderId;
        holder = $(elementId);
      }

      const lockon = new LockOn(elementId);

      if (holder.length > 0 && opts && opts.skipIfOnScreen){
        const elementTop = lockon.elementTop();
        const scrollTop = $(window).scrollTop();
        const windowHeight = $(window).height() - offsetCalculator();
        const height = holder.height();

        if (elementTop > scrollTop && (elementTop + height) < (scrollTop + windowHeight)) {
          return;
        }
      }

      lockon.lock();
    });
  },

  // Browser aware replaceState. Will only be invoked if the browser supports it.
  replaceState(path) {
    if (window.history &&
        window.history.pushState &&
        window.history.replaceState &&
        !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/) &&
        (window.location.pathname !== path)) {

        // Always use replaceState in the next runloop to prevent weird routes changing
        // while URLs are loading. For example, while a topic loads it sets `currentPost`
        // which triggers a replaceState even though the topic hasn't fully loaded yet!
        Ember.run.next(() => {
          const location = DiscourseURL.get('router.location');
          if (location && location.replaceURL) {
            location.replaceURL(path);
          }
        });
    }
  },

  routeToTag(a) {
    if (a && a.host !== document.location.host) {
      document.location = a.href;
      return false;
    }

    return this.routeTo(a.href);
  },

  /**
    Our custom routeTo method is used to intelligently overwrite default routing
    behavior.

    It contains the logic necessary to route within a topic using replaceState to
    keep the history intact.
  **/
  routeTo(path, opts) {
    opts = opts || {};

    if (Em.isEmpty(path)) { return; }

    if (Discourse.get('requiresRefresh')) {
      document.location.href = Discourse.getURL(path);
      return;
    }

    const serverSide = SERVER_SIDE_ONLY.some(r => {
      if (path.match(r)) {
        document.location = path;
        return true;
      }
    });

    if (serverSide) { return; }

    // Protocol relative URLs
    if (path.indexOf('//') === 0) {
      document.location = path;
      return;
    }

    // Scroll to the same page, different anchor
    const m = /^#(.+)$/.exec(path);
    if (m) {
      jumpToElement(m[1]);
      return this.replaceState(path);
    }

    const oldPath = window.location.pathname;
    path = path.replace(/(https?\:)?\/\/[^\/]+/, '');

    // handle prefixes
    if (path.match(/^\//)) {
      let rootURL = (Discourse.BaseUri === undefined ? "/" : Discourse.BaseUri);
      rootURL = rootURL.replace(/\/$/, '');
      path = path.replace(rootURL, '');
    }

    // Rewrite /my/* urls
    if (path.indexOf('/my/') === 0) {
      const currentUser = Discourse.User.current();
      if (currentUser) {
        path = path.replace('/my/', '/users/' + currentUser.get('username_lower') + "/");
      } else {
        document.location.href = "/404";
        return;
      }
    }

    rewrites.forEach(rw => path = path.replace(rw.regexp, rw.replacement));

    if (this.navigatedToPost(oldPath, path, opts)) { return; }

    // Schedule a DOM cleanup event
    Em.run.scheduleOnce('afterRender', Discourse.Route, 'cleanDOM');

    if (oldPath === path) {
      // If navigating to the same path send an app event. Views can watch it
      // and tell their controllers to refresh
      this.appEvents.trigger('url:refresh');
    }

    // TODO: Extract into rules we can inject into the URL handler
    if (this.navigatedToHome(oldPath, path, opts)) { return; }

    return this.handleURL(path, opts);
  },

  rewrite(regexp, replacement) {
    rewrites.push({ regexp, replacement });
  },

  redirectTo(url) {
    window.location = Discourse.getURL(url);
  },

  /**
   * Determines whether a URL is internal or not
   *
   * @method isInternal
   * @param {String} url
  **/
  isInternal(url) {
    if (url && url.length) {
      if (url.indexOf('//') === 0) { url = "http:" + url; }
      if (url.indexOf('#') === 0) { return true; }
      if (url.indexOf('/') === 0) { return true; }
      if (url.indexOf(this.origin()) === 0) { return true; }
      if (url.replace(/^http/, 'https').indexOf(this.origin()) === 0) { return true; }
      if (url.replace(/^https/, 'http').indexOf(this.origin()) === 0) { return true; }
    }
    return false;
  },

  /**
    If the URL is in the topic form, /t/something/:topic_id/:post_number
    then we want to apply some special logic. If the post_number changes within the
    same topic, use replaceState and instruct our controller to load more posts.
  **/
  navigatedToPost(oldPath, path, routeOpts) {
    const newMatches = TOPIC_REGEXP.exec(path);
    const newTopicId = newMatches ? newMatches[2] : null;

    if (newTopicId) {
      const oldMatches = TOPIC_REGEXP.exec(oldPath);
      const oldTopicId = oldMatches ? oldMatches[2] : null;

      // If the topic_id is the same
      if (oldTopicId === newTopicId) {
        DiscourseURL.replaceState(path);

        const container = Discourse.__container__;
        const topicController = container.lookup('controller:topic');
        const opts = {};
        const postStream = topicController.get('model.postStream');

        if (newMatches[3]) { opts.nearPost = newMatches[3]; }
        if (path.match(/last$/)) { opts.nearPost = topicController.get('model.highest_post_number'); }
        const closest = opts.nearPost || 1;

        opts.cancelSummary = true;

        postStream.refresh(opts).then(() => {
          topicController.setProperties({
            'model.currentPost': closest,
            enteredAt: new Date().getTime().toString()
          });

          this.appEvents.trigger('post:highlight', closest);
        }).then(() => {
          const jumpOpts = {
            skipIfOnScreen: routeOpts.skipIfOnScreen
          };

          const m = /#.+$/.exec(path);
          if (m) {
            jumpOpts.anchor = m[0];
          }

          this.jumpToPost(closest, jumpOpts);
        });

        // Abort routing, we have replaced our state.
        return true;
      }
    }

    return false;
  },

  /**
    @private

    Handle the custom case of routing to the root path from itself.

    @param {String} oldPath the previous path we were on
    @param {String} path the path we're navigating to
  **/
  navigatedToHome(oldPath, path) {
    const homepage = defaultHomepage();

    if (window.history &&
        window.history.pushState &&
        (path === "/" || path === "/" + homepage) &&
        (oldPath === "/" || oldPath === "/" + homepage)) {
      this.appEvents.trigger('url:refresh');
      return true;
    }

    return false;
  },

  // This has been extracted so it can be tested.
  origin() {
    return window.location.origin + (Discourse.BaseUri === "/" ? '' : Discourse.BaseUri);
  },

  /**
    @private

    Get a handle on the application's router. Note that currently it uses `__container__` which is not
    advised but there is no other way to access the router.

    @property router
  **/
  router: function() {
    return Discourse.__container__.lookup('router:main');
  }.property().volatile(),

  // Get a controller. Note that currently it uses `__container__` which is not
  // advised but there is no other way to access the router.
  controllerFor(name) {
    return Discourse.__container__.lookup('controller:' + name);
  },

  /**
    Be wary of looking up the router. In this case, we have links in our
    HTML, say form compiled markdown posts, that need to be routed.
  **/
  handleURL(path, opts) {
    opts = opts || {};

    const router = this.get('router');

    if (opts.replaceURL) {
      this.replaceState(path);
    } else {
      router.router.updateURL(path);
    }

    const split = path.split('#');
    let elementId;

    if (split.length === 2) {
      path = split[0];
      elementId = split[1];
    }

    const transition = router.handleURL(path);
    transition._discourse_intercepted = true;
    transition.promise.then(() => jumpToElement(elementId));
  }
}).create();

export default DiscourseURL;
