const Resolver = require("./Resolver");

/* eslint-disable no-throw-literal, class-methods-use-this */
/**
 * The resolver that is used for arguments.
 * @extends Resolver
 */
class ArgResolver extends Resolver {

  /**
   * Resolves a message
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @param {Message} msg The message that triggered the command
   * @returns {external:Message}
   */
  message(...args) {
    return this.msg(...args);
  }

  /**
   * Resolves a message
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @param {Message} msg The message that triggered the command
   * @returns {external:Message}
   */
  async msg(arg, currentUsage, possible, repeat, msg) {
    const message = await super.msg(arg, msg.channel);
    if (message) return message;
    if (currentUsage.type === "optional" && !repeat) return null;
    throw `${currentUsage.possibles[possible].name} must be a valid message id.`;
  }

  messages(...args) {
    return this.msgs(...args);
  }

  async msgs(arg, currentUsage, possible, repeat, msg) {
    const messages = await super.messages(arg, msg.channel, currentUsage.possibles[possible].min);
    if (messages.size > 0) return messages;
    if (currentUsage.type === "optional" && !repeat) return null;
    throw `${currentUsage.possibles[possible].name} must be a valid message id.`;
  }

  /**
   * Resolves a user
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @returns {external:User}
   */
  mention(...args) {
    return this.user(...args);
  }

  /**
   * Resolves a user
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @returns {external:User}
   */
  async user(arg, currentUsage, possible, repeat) {
    const user = await super.user(arg);
    if (user) return user;
    if (currentUsage.type === "optional" && !repeat) return null;
    throw `${currentUsage.possibles[possible].name} must be a mention or valid user id.`;
  }

  /**
   * Resolves a member
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @param {Message} msg The message that triggered the command
   * @returns {external:GuildMember}
   */
  async member(arg, currentUsage, possible, repeat, msg) {
    const member = await super.member(arg, msg.guild);
    if (member) return member;
    if (currentUsage.type === "optional" && !repeat) return null;
    throw `${currentUsage.possibles[possible].name} must be a mention or valid user id.`;
  }

  /**
   * Resolves a channel
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @returns {external:Channel}
   */
  async channel(arg, currentUsage, possible, repeat) {
    const channel = await super.channel(arg);
    if (channel) return channel;
    if (currentUsage.type === "optional" && !repeat) return null;
    throw `${currentUsage.possibles[possible].name} must be a channel tag or valid channel id.`;
  }

  /**
   * Resolves a guild
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @returns {external:Guild}
   */
  async guild(arg, currentUsage, possible, repeat) {
    const guild = await super.guild(arg);
    if (guild) return guild;
    if (currentUsage.type === "optional" && !repeat) return null;
    throw `${currentUsage.possibles[possible].name} must be a valid guild id.`;
  }

  /**
   * Resolves a role
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @param {Message} msg The message that triggered the command
   * @returns {external:Role}
   */
  async role(arg, currentUsage, possible, repeat, msg) {
    const role = await super.role(arg, msg.guild);
    if (role) return role;
    if (currentUsage.type === "optional" && !repeat) return null;
    throw `${currentUsage.possibles[possible].name} must be a role mention or role id.`;
  }

  /**
   * Resolves a literal
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @returns {string}
   */
  async literal(arg, currentUsage, possible, repeat) {
    if (arg.toLowerCase() === currentUsage.possibles[possible].name.toLowerCase()) return arg.toLowerCase();
    if (currentUsage.type === "optional" && !repeat) return null;
    throw [
      `Your option did not literally match the only possibility: (${currentUsage.possibles.map(poss => poss.name).join(", ")})`,
      "This is likely caused by a mistake in the usage string.",
    ].join("\n");
  }

  /**
   * Resolves a boolean
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @returns {boolean}
   */
  bool(...args) {
    return this.boolean(...args);
  }

  /**
   * Resolves a boolean
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @returns {boolean}
   */
  async boolean(arg, currentUsage, possible, repeat) {
    const boolean = await super.boolean(arg);
    if (boolean !== null) return boolean;
    if (currentUsage.type === "optional" && !repeat) return null;
    throw `${currentUsage.possibles[possible].name} must be true or false.`;
  }

  /**
   * Resolves a string
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @returns {string}
   */
  str(...args) {
    return this.string(...args);
  }

  /**
   * Resolves a string
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @returns {string}
   */
  async string(arg, currentUsage, possible, repeat) {
    const { min, max } = currentUsage.possibles[possible];
    if (min && max) {
      if (arg.length >= min && arg.length <= max) return arg;
      if (currentUsage.type === "optional" && !repeat) return null;
      if (min === max) throw `${currentUsage.possibles[possible].name} must be exactly ${min} characters.`;
      throw `${currentUsage.possibles[possible].name} must be between ${min} and ${max} characters.`;
    } else if (min) {
      if (arg.length >= min) return arg;
      if (currentUsage.type === "optional" && !repeat) return null;
      throw `${currentUsage.possibles[possible].name} must be longer than ${min} characters.`;
    } else if (max) {
      if (arg.length <= max) return arg;
      if (currentUsage.type === "optional" && !repeat) return null;
      throw `${currentUsage.possibles[possible].name} must be shorter than ${max} characters.`;
    }
    return arg;
  }

  /**
   * Resolves a integer
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @returns {number}
   */
  int(...args) {
    return this.integer(...args);
  }

  /**
   * Resolves a integer
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @returns {number}
   */
  async integer(arg, currentUsage, possible, repeat) {
    const { min, max } = currentUsage.possibles[possible];
    arg = await super.integer(arg);
    if (arg === null) {
      if (currentUsage.type === "optional" && !repeat) return null;
      throw `${currentUsage.possibles[possible].name} must be an integer.`;
    } else if (min && max) {
      if (arg >= min && arg <= max) return arg;
      if (currentUsage.type === "optional" && !repeat) return null;
      if (min === max) throw `${currentUsage.possibles[possible].name} must be exactly ${min}\nSo why didn't the dev use a literal?`;
      throw `${currentUsage.possibles[possible].name} must be between ${min} and ${max}.`;
    } else if (min) {
      if (arg >= min) return arg;
      if (currentUsage.type === "optional" && !repeat) return null;
      throw `${currentUsage.possibles[possible].name} must be greater than ${min}.`;
    } else if (max) {
      if (arg <= max) return arg;
      if (currentUsage.type === "optional" && !repeat) return null;
      throw `${currentUsage.possibles[possible].name} must be less than ${max}.`;
    }
    return arg;
  }

  /**
   * Resolves a number
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @returns {number}
   */
  num(...args) {
    return this.float(...args);
  }

  /**
   * Resolves a number
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @returns {number}
   */
  number(...args) {
    return this.float(...args);
  }

  /**
   * Resolves a number
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @returns {number}
   */
  async float(arg, currentUsage, possible, repeat) {
    const { min, max } = currentUsage.possibles[possible];
    arg = await super.float(arg);
    if (arg === null) {
      if (currentUsage.type === "optional" && !repeat) return null;
      throw `${currentUsage.possibles[possible].name} must be a valid number.`;
    } else if (min && max) {
      if (arg >= min && arg <= max) return arg;
      if (currentUsage.type === "optional" && !repeat) return null;
      if (min === max) throw `${currentUsage.possibles[possible].name} must be exactly ${min}\nSo why didn't the dev use a literal?`;
      throw `${currentUsage.possibles[possible].name} must be between ${min} and ${max}.`;
    } else if (min) {
      if (arg >= min) return arg;
      if (currentUsage.type === "optional" && !repeat) return null;
      throw `${currentUsage.possibles[possible].name} must be greater than ${min}.`;
    } else if (max) {
      if (arg <= max) return arg;
      if (currentUsage.type === "optional" && !repeat) return null;
      throw `${currentUsage.possibles[possible].name} must be less than ${max}.`;
    }
    return arg;
  }

  /**
   * Resolves a hyperlink
   * @param {string} arg This arg
   * @param {Object} currentUsage This current usage
   * @param {number} possible This possible usage id
   * @param {boolean} repeat If it is a looping/repeating arg
   * @returns {string}
   */
  async url(arg, currentUsage, possible, repeat) {
    const hyperlink = await super.url(arg);
    if (hyperlink !== null) return hyperlink;
    if (currentUsage.type === "optional" && !repeat) return null;
    throw `${currentUsage.possibles[possible].name} must be a valid url.`;
  }

}

module.exports = ArgResolver;