module GameData
  class Species
    attr_reader :id
    attr_reader :id_number
    attr_reader :species
    attr_reader :form
    attr_reader :real_name
    attr_reader :real_form_name
    attr_reader :real_category
    attr_reader :real_pokedex_entry
    attr_reader :pokedex_form
    attr_reader :type1
    attr_reader :type2
    attr_reader :base_stats
    attr_reader :evs
    attr_reader :base_exp
    attr_reader :growth_rate
    attr_reader :gender_ratio
    attr_reader :catch_rate
    attr_reader :happiness
    attr_reader :moves
    attr_reader :tutor_moves
    attr_reader :egg_moves
    attr_reader :abilities
    attr_reader :hidden_abilities
    attr_reader :wild_item_common
    attr_reader :wild_item_uncommon
    attr_reader :wild_item_rare
    attr_reader :egg_groups
    attr_reader :hatch_steps
    attr_reader :incense
    attr_reader :evolutions
    attr_reader :height
    attr_reader :weight
    attr_reader :color
    attr_reader :shape
    attr_reader :habitat
    attr_reader :generation
    attr_reader :mega_stone
    attr_reader :mega_move
    attr_reader :unmega_form
    attr_reader :mega_message
    attr_accessor :back_sprite_x
    attr_accessor :back_sprite_y
    attr_accessor :front_sprite_x
    attr_accessor :front_sprite_y
    attr_accessor :front_sprite_altitude
    attr_accessor :shadow_x
    attr_accessor :shadow_size
    attr_accessor :alwaysUseGeneratedSprite

    DATA = {}
    DATA_FILENAME = "species.dat"

    extend ClassMethods
    include InstanceMethods

    # @param species [Symbol, self, String, Integer]
    # @param form [Integer]
    # @return [self, nil]
    def self.get_species_form(species, form)
      return GameData::Species.get(species) rescue nil
      # return nil if !species || !form
      # validate species => [Symbol, self, String, Integer]
      # validate form => Integer
      # #      if other.is_a?(Integer)
      # #        p "Please switch to symbols, thanks."
      # #      end
      # species = species.species if species.is_a?(self)
      # species = DATA[species].species if species.is_a?(Integer)
      # species = species.to_sym if species.is_a?(String)
      # trial = sprintf("%s_%d", species, form).to_sym
      # species_form = (DATA[trial].nil?) ? species : trial
      # return (DATA.has_key?(species_form)) ? DATA[species_form] : nil
    end

    def self.schema(compiling_forms = false)
      ret = {
        "FormName" => [0, "q"],
        "Kind" => [0, "s"],
        "Pokedex" => [0, "q"],
        "Type1" => [0, "e", :Type],
        "Type2" => [0, "e", :Type],
        "BaseStats" => [0, "vvvvvv"],
        "EffortPoints" => [0, "uuuuuu"],
        "BaseEXP" => [0, "v"],
        "Rareness" => [0, "u"],
        "Happiness" => [0, "u"],
        "Moves" => [0, "*ue", nil, :Move],
        "TutorMoves" => [0, "*e", :Move],
        "EggMoves" => [0, "*e", :Move],
        "Abilities" => [0, "*e", :Ability],
        "HiddenAbility" => [0, "*e", :Ability],
        "WildItemCommon" => [0, "e", :Item],
        "WildItemUncommon" => [0, "e", :Item],
        "WildItemRare" => [0, "e", :Item],
        "Compatibility" => [0, "*e", :EggGroup],
        "StepsToHatch" => [0, "v"],
        "Height" => [0, "f"],
        "Weight" => [0, "f"],
        "Color" => [0, "e", :BodyColor],
        "Shape" => [0, "y", :BodyShape],
        "Habitat" => [0, "e", :Habitat],
        "Generation" => [0, "i"],
        "BattlerPlayerX" => [0, "i"],
        "BattlerPlayerY" => [0, "i"],
        "BattlerEnemyX" => [0, "i"],
        "BattlerEnemyY" => [0, "i"],
        "BattlerAltitude" => [0, "i"],
        "BattlerShadowX" => [0, "i"],
        "BattlerShadowSize" => [0, "u"]
      }
      if compiling_forms
        ret["PokedexForm"] = [0, "u"]
        ret["Evolutions"] = [0, "*ees", :Species, :Evolution, nil]
        ret["MegaStone"] = [0, "e", :Item]
        ret["MegaMove"] = [0, "e", :Move]
        ret["UnmegaForm"] = [0, "u"]
        ret["MegaMessage"] = [0, "u"]
      else
        ret["InternalName"] = [0, "n"]
        ret["Name"] = [0, "s"]
        ret["GrowthRate"] = [0, "e", :GrowthRate]
        ret["GenderRate"] = [0, "e", :GenderRatio]
        ret["Incense"] = [0, "e", :Item]
        ret["Evolutions"] = [0, "*ses", nil, :Evolution, nil]
      end
      return ret
    end

    def initialize(hash)
      @id = hash[:id]
      @id_number = hash[:id_number] || -1
      @species = hash[:species] || @id
      @form = hash[:form] || 0
      @real_name = hash[:name] || "Unnamed"
      @real_form_name = hash[:form_name]
      @real_category = hash[:category] || "???"
      @real_pokedex_entry = hash[:pokedex_entry] || "???"
      @pokedex_form = hash[:pokedex_form] || @form
      @type1 = hash[:type1] || :NORMAL
      @type2 = hash[:type2] || @type1
      @base_stats = hash[:base_stats] || {}
      @evs = hash[:evs] || {}
      GameData::Stat.each_main do |s|
        @base_stats[s.id] = 1 if !@base_stats[s.id] || @base_stats[s.id] <= 0
        @evs[s.id] = 0 if !@evs[s.id] || @evs[s.id] < 0
      end
      @base_exp = hash[:base_exp] || 100
      @growth_rate = hash[:growth_rate] || :Medium
      @gender_ratio = hash[:gender_ratio] || :Female50Percent
      @catch_rate = hash[:catch_rate] || 255
      @happiness = hash[:happiness] || 70
      @moves = hash[:moves] || []
      @tutor_moves = hash[:tutor_moves] || []
      @egg_moves = hash[:egg_moves] || []
      @abilities = hash[:abilities] || []
      @hidden_abilities = hash[:hidden_abilities] || []
      @wild_item_common = hash[:wild_item_common]
      @wild_item_uncommon = hash[:wild_item_uncommon]
      @wild_item_rare = hash[:wild_item_rare]
      @egg_groups = hash[:egg_groups] || [:Undiscovered]
      @hatch_steps = hash[:hatch_steps] || 1
      @incense = hash[:incense]
      @evolutions = hash[:evolutions] || []
      @height = hash[:height] || 1
      @weight = hash[:weight] || 1
      @color = hash[:color] || :Red
      @shape = hash[:shape] || :Head
      @habitat = hash[:habitat] || :None
      @generation = hash[:generation] || 0
      @mega_stone = hash[:mega_stone]
      @mega_move = hash[:mega_move]
      @unmega_form = hash[:unmega_form] || 0
      @mega_message = hash[:mega_message] || 0
      @back_sprite_x = hash[:back_sprite_x] || 0
      @back_sprite_y = hash[:back_sprite_y] || 0
      @front_sprite_x = hash[:front_sprite_x] || 0
      @front_sprite_y = hash[:front_sprite_y] || 0
      @front_sprite_altitude = hash[:front_sprite_altitude] || 0
      @shadow_x = hash[:shadow_x] || 0
      @shadow_size = hash[:shadow_size] || 2
      @alwaysUseGeneratedSprite=false
    end

    def set_always_use_generated_sprite(useGeneratedSprite)
      @alwaysUseGeneratedSprite=useGeneratedSprite
    end

    def always_use_generated
      return @alwaysUseGeneratedSprite
    end
    # @return [String] the translated name of this species
    def name
      return @real_name
      #return pbGetMessage(MessageTypes::Species, @id_number)
    end

    # @return [String] the translated name of this form of this species
    def form_name
      return @real_form_name
      #return pbGetMessage(MessageTypes::FormNames, @id_number)
    end

    # @return [String] the translated Pokédex category of this species
    def category
      return @real_category
      #return pbGetMessage(MessageTypes::Kinds, @id_number)
    end

    # @return [String] the translated Pokédex entry of this species
    def pokedex_entry
      return @real_pokedex_entry
      #return pbGetMessage(MessageTypes::Entries, @id_number)
    end

    def is_fusion
      return @id_number > Settings::NB_POKEMON
    end

    def is_triple_fusion
      return @id_number >= Settings::ZAPMOLCUNO_NB
    end
    def get_body_species
      return @species
    end

    def get_head_species
      return @species
    end

    def hasType?(type)
      type = GameData::Type.get(type).id
      return self.types.include?(type)
    end

    def types
      types = [@type1]
      types << @type2 if @type2 && @type2 != @type1
      return types
    end

    def apply_metrics_to_sprite(sprite, index, shadow = false)
      front_sprite_y = self.is_fusion ? GameData::Species.get(getBodyID(@id_number)).front_sprite_y: @front_sprite_y

      if shadow
        if (index & 1) == 1 # Foe Pokémon
          sprite.x += @shadow_x * 2
        end
      else
        if (index & 1) == 0 # Player's Pokémon
          sprite.x += @back_sprite_x * 2
          sprite.y += (@back_sprite_y * 2) + Settings::BACKSPRITE_POSITION_OFFSET
        else
          # Foe Pokémon
          sprite.x += @front_sprite_x * 2
          sprite.y += (front_sprite_y * 2) + Settings::FRONTSPRITE_POSITION_OFFSET
          sprite.y -= @front_sprite_altitude * 2
        end
      end
    end

    def shows_shadow?
      return true
      #      return @front_sprite_altitude > 0
    end

    def get_evolutions(exclude_invalid = false)
      ret = []
      @evolutions.each do |evo|
        next if evo[3] # Is the prevolution
        next if evo[1] == :None && exclude_invalid
        ret.push([evo[0], evo[1], evo[2]]) # [Species, method, parameter]
      end
      return ret
    end

    def get_family_evolutions(exclude_invalid = true)
      evos = self.get_evolutions(exclude_invalid)
      evos = evos.sort { |a, b| GameData::Species.get(a[0]).id_number <=> GameData::Species.get(b[0]).id_number }
      ret = []
      evos.each do |evo|
        ret.push([@species].concat(evo)) # [Prevo species, evo species, method, parameter]
        evo_array = GameData::Species.get(evo[0]).get_family_evolutions(exclude_invalid)
        ret.concat(evo_array) if evo_array && evo_array.length > 0
      end
      return ret
    end

    def get_previous_species
      return @species if @evolutions.length == 0
      @evolutions.each { |evo| return evo[0] if evo[3] } # Is the prevolution
      return @species
    end

    def get_baby_species(check_items = false, item1 = nil, item2 = nil)
      ret = @species
      return ret if @evolutions.length == 0
      @evolutions.each do |evo|
        next if !evo[3] # Not the prevolution
        if check_items
          incense = GameData::Species.get(evo[0]).incense
          ret = evo[0] if !incense || item1 == incense || item2 == incense
        else
          ret = evo[0] # Species of prevolution
        end
        break
      end
      ret = GameData::Species.get(ret).get_baby_species(check_items, item1, item2) if ret != @species
      return ret
    end

    def get_related_species
      sp = self.get_baby_species
      evos = GameData::Species.get(sp).get_family_evolutions(false)
      return [sp] if evos.length == 0
      return [sp].concat(evos.map { |e| e[1] }).uniq
    end

    def family_evolutions_have_method?(check_method, check_param = nil)
      sp = self.get_baby_species
      evos = GameData::Species.get(sp).get_family_evolutions
      return false if evos.length == 0
      evos.each do |evo|
        if check_method.is_a?(Array)
          next if !check_method.include?(evo[2])
        else
          next if evo[2] != check_method
        end
        return true if check_param.nil? || evo[3] == check_param
      end
      return false
    end

    # Used by the Moon Ball when checking if a Pokémon's evolution family
    # includes an evolution that uses the Moon Stone.
    def family_item_evolutions_use_item?(check_item = nil)
      sp = self.get_baby_species
      evos = GameData::Species.get(sp).get_family_evolutions
      return false if !evos || evos.length == 0
      evos.each do |evo|
        next if GameData::Evolution.get(evo[2]).use_item_proc.nil?
        return true if check_item.nil? || evo[3] == check_item
      end
      return false
    end

    def minimum_level
      return 1 if @evolutions.length == 0
      @evolutions.each do |evo|
        next if !evo[3] # Not the prevolution
        evo_method_data = GameData::Evolution.get(evo[1])
        next if evo_method_data.level_up_proc.nil?
        min_level = evo_method_data.minimum_level
        return (min_level == 0) ? evo[2] : min_level + 1
      end
      return 1
    end
  end
end

#===============================================================================
# Deprecated methods
#===============================================================================
# @deprecated This alias is slated to be removed in v20.
def pbGetSpeciesData(species, form = 0, species_data_type = -1)
  Deprecation.warn_method('pbGetSpeciesData', 'v20', 'GameData::Species.get_species_form(species, form).something')
  return GameData::Species.get_species_form(species, form)
end

# @deprecated This alias is slated to be removed in v20.
def pbGetSpeciesEggMoves(species, form = 0)
  Deprecation.warn_method('pbGetSpeciesEggMoves', 'v20', 'GameData::Species.get_species_form(species, form).egg_moves')
  return GameData::Species.get_species_form(species, form).egg_moves
end

# @deprecated This alias is slated to be removed in v20.
def pbGetSpeciesMoveset(species, form = 0)
  Deprecation.warn_method('pbGetSpeciesMoveset', 'v20', 'GameData::Species.get_species_form(species, form).moves')
  return GameData::Species.get_species_form(species, form).moves
end

# @deprecated This alias is slated to be removed in v20.
def pbGetEvolutionData(species)
  Deprecation.warn_method('pbGetEvolutionData', 'v20', 'GameData::Species.get(species).evolutions')
  return GameData::Species.get(species).evolutions
end

# @deprecated This alias is slated to be removed in v20.
def pbApplyBattlerMetricsToSprite(sprite, index, species_data, shadow = false, metrics = nil)
  Deprecation.warn_method('pbApplyBattlerMetricsToSprite', 'v20', 'GameData::Species.get(species).apply_metrics_to_sprite')
  GameData::Species.get(species).apply_metrics_to_sprite(sprite, index, shadow)
end

# @deprecated This alias is slated to be removed in v20.
def showShadow?(species)
  Deprecation.warn_method('showShadow?', 'v20', 'GameData::Species.get(species).shows_shadow?')
  return GameData::Species.get(species).shows_shadow?
end

# @deprecated Use {GameData#Species#get_evolutions} instead. This alias is slated to be removed in v20.
def pbGetEvolvedFormData(species, exclude_invalid = false)
  Deprecation.warn_method('pbGetEvolvedFormData', 'v20', 'GameData::Species.get(species).get_evolutions')
  return GameData::Species.get(species).get_evolutions(exclude_invalid)
end

# @deprecated Use {GameData#Species#get_family_evolutions} instead. This alias is slated to be removed in v20.
def pbGetEvolutionFamilyData(species)
  # Unused
  Deprecation.warn_method('pbGetEvolutionFamilyData', 'v20', 'GameData::Species.get(species).get_family_evolutions')
  return GameData::Species.get(species).get_family_evolutions
end

# @deprecated Use {GameData#Species#get_previous_species} instead. This alias is slated to be removed in v20.
def pbGetPreviousForm(species)
  # Unused
  Deprecation.warn_method('pbGetPreviousForm', 'v20', 'GameData::Species.get(species).get_previous_species')
  return GameData::Species.get(species).get_previous_species
end

# @deprecated Use {GameData#Species#get_baby_species} instead. This alias is slated to be removed in v20.
def pbGetBabySpecies(species, check_items = false, item1 = nil, item2 = nil)
  Deprecation.warn_method('pbGetBabySpecies', 'v20', 'GameData::Species.get(species).get_baby_species')
  return GameData::Species.get(species).get_baby_species(check_items, item1, item2)
end

# @deprecated Use {GameData#Species#family_evolutions_have_method?} instead. This alias is slated to be removed in v20.
def pbCheckEvolutionFamilyForMethod(species, method, param = nil)
  # Unused
  Deprecation.warn_method('pbCheckEvolutionFamilyForMethod', 'v20', 'GameData::Species.get(species).family_evolutions_have_method?(method)')
  return GameData::Species.get(species).family_evolutions_have_method?(method, param)
end

# @deprecated Use {GameData#Species#family_item_evolutions_use_item?} instead. This alias is slated to be removed in v20.
def pbCheckEvolutionFamilyForItemMethodItem(species, param = nil)
  Deprecation.warn_method('pbCheckEvolutionFamilyForItemMethodItem', 'v20', 'GameData::Species.get(species).family_item_evolutions_use_item?(item)')
  return GameData::Species.get(species).family_item_evolutions_use_item?(param)
end

# @deprecated Use {GameData#Species#minimum_level} instead. This alias is slated to be removed in v20.
def pbGetMinimumLevel(species)
  Deprecation.warn_method('pbGetMinimumLevel', 'v20', 'GameData::Species.get(species).minimum_level')
  return GameData::Species.get(species).minimum_level
end
