module GameData
  class Trainer
    attr_reader :id
    attr_reader :trainer_type
    attr_reader :real_name
    attr_reader :version
    attr_reader :items
    attr_reader :real_lose_text
    attr_reader :pokemon
    attr_reader :pbs_file_suffix

    DATA = {}
    DATA_FILENAME = "trainers.dat"
    PBS_BASE_FILENAME = "trainers"

    # "Pokemon" is specially mentioned in def compile_trainers and def
    # write_trainers, and acts as a subheading for a particular Pokémon.
    SCHEMA = {
      "SectionName" => [:id,             "esU", :TrainerType],
      "Items"       => [:items,          "*e", :Item],
      "LoseText"    => [:real_lose_text, "q"],
      "Pokemon"     => [:pokemon,        "ev", :Species]   # Species, level
    }
    # This schema is for definable properties of individual Pokémon (apart from
    # species and level which are above).
    SUB_SCHEMA = {
      "Form"         => [:form,            "u"],
      "Name"         => [:real_name,       "s"],
      "Moves"        => [:moves,           "*e", :Move],
      "Ability"      => [:ability,         "e", :Ability],
      "AbilityIndex" => [:ability_index,   "u"],
      "Item"         => [:item,            "e", :Item],
      "Gender"       => [:gender,          "e", {"M" => 0, "m" => 0, "Male" => 0, "male" => 0, "0" => 0,
                                                 "F" => 1, "f" => 1, "Female" => 1, "female" => 1, "1" => 1}],
      "Nature"       => [:nature,          "e", :Nature],
      "IV"           => [:iv,              "uUUUUU"],
      "EV"           => [:ev,              "uUUUUU"],
      "Happiness"    => [:happiness,       "u"],
      "Shiny"        => [:shininess,       "b"],
      "SuperShiny"   => [:super_shininess, "b"],
      "Shadow"       => [:shadowness,      "b"],
      "Ball"         => [:poke_ball,       "e", :Item]
    }

    extend ClassMethodsSymbols
    include InstanceMethods

    def self.sub_schema
      return SUB_SCHEMA
    end

    # @param tr_type [Symbol, String]
    # @param tr_name [String]
    # @param tr_version [Integer, nil]
    # @return [Boolean] whether the given other is defined as a self
    def self.exists?(tr_type, tr_name, tr_version = 0)
      validate tr_type => [Symbol, String]
      validate tr_name => [String]
      key = [tr_type.to_sym, tr_name, tr_version]
      return !self::DATA[key].nil?
    end

    # @param tr_type [Symbol, String]
    # @param tr_name [String]
    # @param tr_version [Integer, nil]
    # @return [self]
    def self.get(tr_type, tr_name, tr_version = 0)
      validate tr_type => [Symbol, String]
      validate tr_name => [String]
      key = [tr_type.to_sym, tr_name, tr_version]
      raise "Unknown trainer #{tr_type} #{tr_name} #{tr_version}." unless self::DATA.has_key?(key)
      return self::DATA[key]
    end

    # @param tr_type [Symbol, String]
    # @param tr_name [String]
    # @param tr_version [Integer, nil]
    # @return [self, nil]
    def self.try_get(tr_type, tr_name, tr_version = 0)
      validate tr_type => [Symbol, String]
      validate tr_name => [String]
      key = [tr_type.to_sym, tr_name, tr_version]
      return (self::DATA.has_key?(key)) ? self::DATA[key] : nil
    end

    def initialize(hash)
      @id              = hash[:id]
      @trainer_type    = hash[:trainer_type]
      @real_name       = hash[:real_name]       || ""
      @version         = hash[:version]         || 0
      @items           = hash[:items]           || []
      @real_lose_text  = hash[:real_lose_text]  || "..."
      @pokemon         = hash[:pokemon]         || []
      @pokemon.each do |pkmn|
        GameData::Stat.each_main do |s|
          pkmn[:iv][s.id] ||= 0 if pkmn[:iv]
          pkmn[:ev][s.id] ||= 0 if pkmn[:ev]
        end
      end
      @pbs_file_suffix = hash[:pbs_file_suffix] || ""
    end

    # @return [String] the translated name of this trainer
    def name
      return pbGetMessageFromHash(MessageTypes::TRAINER_NAMES, @real_name)
    end

    # @return [String] the translated in-battle lose message of this trainer
    def lose_text
      return pbGetMessageFromHash(MessageTypes::TRAINER_SPEECHES_LOSE, @real_lose_text)
    end

    # Creates a battle-ready version of a trainer's data.
    # @return [Array] all information about a trainer in a usable form
    def to_trainer
      # Determine trainer's name
      tr_name = self.name
      Settings::RIVAL_NAMES.each do |rival|
        next if rival[0] != @trainer_type || !$game_variables[rival[1]].is_a?(String)
        tr_name = $game_variables[rival[1]]
        break
      end
      # Create trainer object
      trainer = NPCTrainer.new(tr_name, @trainer_type, @version)
      trainer.id        = $player.make_foreign_ID
      trainer.items     = @items.clone
      trainer.lose_text = self.lose_text
      # Create each Pokémon owned by the trainer
      @pokemon.each do |pkmn_data|
        species = GameData::Species.get(pkmn_data[:species]).species
        pkmn = Pokemon.new(species, pkmn_data[:level], trainer, false)
        trainer.party.push(pkmn)
        # Set Pokémon's properties if defined
        pkmn.form_simple = pkmn_data[:form] if pkmn_data[:form]
        pkmn.item = pkmn_data[:item]
        if pkmn_data[:moves] && pkmn_data[:moves].length > 0
          pkmn_data[:moves].each { |move| pkmn.learn_move(move) }
        else
          pkmn.reset_moves
        end
        pkmn.ability_index = pkmn_data[:ability_index] || 0
        pkmn.ability = pkmn_data[:ability]
        pkmn.gender = pkmn_data[:gender] || ((trainer.male?) ? 0 : 1)
        pkmn.shiny = (pkmn_data[:shininess]) ? true : false
        pkmn.super_shiny = (pkmn_data[:super_shininess]) ? true : false
        if pkmn_data[:nature]
          pkmn.nature = pkmn_data[:nature]
        else   # Make the nature random but consistent for the same species used by the same trainer type
          species_num = GameData::Species.keys.index(species) || 1
          tr_type_num = GameData::TrainerType.keys.index(@trainer_type) || 1
          idx = (species_num + tr_type_num) % GameData::Nature.count
          pkmn.nature = GameData::Nature.get(GameData::Nature.keys[idx]).id
        end
        GameData::Stat.each_main do |s|
          if pkmn_data[:iv]
            pkmn.iv[s.id] = pkmn_data[:iv][s.id]
          else
            pkmn.iv[s.id] = [pkmn_data[:level] / 2, Pokemon::IV_STAT_LIMIT].min
          end
          if pkmn_data[:ev]
            pkmn.ev[s.id] = pkmn_data[:ev][s.id]
          else
            pkmn.ev[s.id] = [pkmn_data[:level] * 3 / 2, Pokemon::EV_LIMIT / 6].min
          end
        end
        pkmn.happiness = pkmn_data[:happiness] if pkmn_data[:happiness]
        if !nil_or_empty?(pkmn_data[:real_name])
          pkmn.name = pbGetMessageFromHash(MessageTypes::POKEMON_NICKNAMES, pkmn_data[:real_name])
        end
        if pkmn_data[:shadowness]
          pkmn.makeShadow
          pkmn.shiny = false
        end
        pkmn.poke_ball = pkmn_data[:poke_ball] if pkmn_data[:poke_ball]
        pkmn.calc_stats
      end
      return trainer
    end

    alias __orig__get_property_for_PBS get_property_for_PBS unless method_defined?(:__orig__get_property_for_PBS)
    def get_property_for_PBS(key, index = 0)
      ret = __orig__get_property_for_PBS(key)
      case key
      when "SectionName"
        ret = [@trainer_type, @real_name] if @version == 0
      when "Pokemon"
        ret = [@pokemon[index][:species], @pokemon[index][:level]]
      end
      return ret
    end

    def get_pokemon_property_for_PBS(key, index = 0)
      return [@pokemon[index][:species], @pokemon[index][:level]] if key == "Pokemon"
      ret = @pokemon[index][SUB_SCHEMA[key][0]]
      ret = nil if ret == false || (ret.is_a?(Array) && ret.length == 0) || ret == ""
      case key
      when "Gender"
        ret = ["male", "female"][ret] if ret
      when "IV", "EV"
        if ret
          new_ret = []
          GameData::Stat.each_main do |s|
            new_ret[s.pbs_order] = ret[s.id] if s.pbs_order >= 0
          end
          ret = new_ret
        end
      when "Shiny"
        ret = nil if @pokemon[index][:super_shininess]
      end
      return ret
    end
  end
end
