/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { Class } = require("../core/heritage");
const { extend } = require("../util/object");
const { MatchPattern } = require("../util/match-pattern");
const readers = require("./readers");

// Context class is required to implement a single `isCurrent(target)` method
// that must return boolean value indicating weather given target matches a
// context or not. Most context implementations below will have an associated
// reader that way context implementation can setup a reader to extract necessary
// information to make decision if target is matching a context.
const Context = Class({
  isRequired: false,
  isCurrent(target) {
    throw Error("Context class must implement isCurrent(target) method");
  },
  get required() {
    Object.defineProperty(this, "required", {
      value: Object.assign(Object.create(Object.getPrototypeOf(this)),
                           this,
                           {isRequired: true})
    });
    return this.required;
  }
});
Context.required = function(...params) {
  return Object.assign(new this(...params), {isRequired: true});
};
exports.Context = Context;


// Next few context implementations use an associated reader to extract info
// from the context target and story it to a private symbol associtaed with
// a context implementation. That way name collisions are avoided while required
// information is still carried along.
const isPage = Symbol("context/page?")
const PageContext = Class({
  extends: Context,
  read: {[isPage]: new readers.isPage()},
  isCurrent: target => target[isPage]
});
exports.Page = PageContext;

const isFrame = Symbol("context/frame?");
const FrameContext = Class({
  extends: Context,
  read: {[isFrame]: new readers.isFrame()},
  isCurrent: target => target[isFrame]
});
exports.Frame = FrameContext;

const selection = Symbol("context/selection")
const SelectionContext = Class({
  read: {[selection]: new readers.Selection()},
  isCurrent: target => !!target[selection]
});
exports.Selection = SelectionContext;

const link = Symbol("context/link");
const LinkContext = Class({
  extends: Context,
  read: {[link]: new readers.LinkURL()},
  isCurrent: target => !!target[link]
});
exports.Link = LinkContext;

const isEditable = Symbol("context/editable?")
const EditableContext = Class({
  extends: Context,
  read: {[isEditable]: new readers.isEditable()},
  isCurrent: target => target[isEditable]
});
exports.Editable = EditableContext;


const mediaType = Symbol("context/mediaType")

const ImageContext = Class({
  extends: Context,
  read: {[mediaType]: new readers.MediaType()},
  isCurrent: target => target[mediaType] === "image"
});
exports.Image = ImageContext;


const VideoContext = Class({
  extends: Context,
  read: {[mediaType]: new readers.MediaType()},
  isCurrent: target => target[mediaType] === "video"
});
exports.Video = VideoContext;


const AudioContext = Class({
  extends: Context,
  read: {[mediaType]: new readers.MediaType()},
  isCurrent: target => target[mediaType] === "audio"
});
exports.Audio = AudioContext;

const isSelectorMatch = Symbol("context/selector/mathches?")
const SelectorContext = Class({
  extends: Context,
  initialize(selector) {
    this.selector = selector;
    // Each instance of selector context will need to store read
    // data into different field, so that case with multilpe selector
    // contexts won't cause a conflicts.
    this[isSelectorMatch] = Symbol(selector);
    this.read = {[this[isSelectorMatch]]: new readers.SelectorMatch(selector)};
  },
  isCurrent(target) {
    return target[this[isSelectorMatch]];
  }
});
exports.Selector = SelectorContext;

const url = Symbol("context/url");
const URLContext = Class({
  extends: Context,
  initialize(pattern) {
    this.pattern = new MatchPattern(pattern);
  },
  read: {[url]: new readers.PageURL()},
  isCurrent(target) {
    return this.pattern.test(target[url]);
  }
});
exports.URL = URLContext;

var PredicateContext = Class({
  extends: Context,
  initialize(isMatch) {
    if (typeof(isMatch) !== "function") {
      throw TypeError("Predicate context mus be passed a function");
    }

    this.isMatch = isMatch
  },
  isCurrent(target) {
    return this.isMatch(target);
  }
});
exports.Predicate = PredicateContext;