class PurifyChamberSet
  attr_accessor :facing
  attr_accessor :shadow

  def partialSum(x)
    return (x * x + x) / 2 # pattern: 1 3 6 10 15 21 28 ...
  end

  def length
    return @list.length
  end

  def initialize
    @list = []
    @facing = 0
  end

  def facing=(value)
    if value >= 0 && value < @list.length
      @facing = value
    end
  end

  def shadow=(value)
    if value == nil || value.isShadow?
      @shadow = value
    end
  end

=begin
Main component is tempo
Boosted if center has advantage over facing Pokemon
Boosted based on number of best circles
=end
  def flow
    ret = 0
    return 0 if !@shadow

    for i in 0...@list.length
      ret += (PurifyChamberSet.isSuperEffective(@list[i], @list[(i + 1) % @list.length])) ? 1 : 0
    end
    if @list[@facing]
      ret += PurifyChamberSet.isSuperEffective(@shadow, @list[@facing]) ? 1 : 0
    end
    return [ret + (@list.length / 2), 4].min
  end

  def shadowAffinity
    return 0 if @facing < 0 || @facing >= @list.length || !@shadow

    return (PurifyChamberSet.isSuperEffective(@shadow, @list[@facing])) ? 2 : 1
  end

  def affinity(i)
    return 0 if i < 0 || i >= @list.length

    return (PurifyChamberSet.isSuperEffective(@list[i], @list[(i + 1) % @list.length])) ? 2 : 1
  end

  # Tempo refers to the type advantages of each Pokemon in a certain set in a
  # clockwise direction. Tempo also depends on the number of Pokemon in the set
  def tempo
    ret = 0
    for i in 0...@list.length
      ret += (PurifyChamberSet.isSuperEffective(@list[i], @list[(i + 1) % @list.length])) ? 1 : 0
    end
    return partialSum(@list.length) + ret
  end

  def list
    return @list.clone
  end

  def [](index)
    return @list[index]
  end

  def insertAfter(index, value)
    return if self.length >= PurifyChamber::SETSIZE
    return if index < 0 || index >= PurifyChamber::SETSIZE

    if !value || !value.isShadow?
      @list.insert(index + 1, value)
      @list.compact!
      if @facing > index && value != nil
        @facing += 1
      end
      @facing = [[@facing, @list.length - 1].min, 0].max
    end
  end

  def insertAt(index, value)
    return if index < 0 || index >= PurifyChamber::SETSIZE

    if !value || !value.isShadow?
      @list[index] = value
      @list.compact!
      @facing = [[@facing, @list.length - 1].min, 0].max
    end
  end

  # Purify Chamber treats Normal/Normal matchup as super effective
  def self.typeAdvantage(p1, p2)
    if p1 == :NORMAL && p2 == :NORMAL
      return true
    end

    return PBTypes.isTypeSE?(p1, p2)
  end

  def self.isSuperEffective(p1, p2)
    if typeAdvantage(p1.type1, p2.type1) || typeAdvantage(p1.type1, p2.type2)
      return true
    end

    return false
  end
end

class PurifyChamber # German: der Kryptorbis
  NUMSETS = 9
  SETSIZE = 4
  attr_accessor :currentSet # German: das Forum

  def self.maximumTempo() # Calculates the maximum possible tempo
    x = SETSIZE + 1
    return ((x * x + x) / 2) - 1
  end

  def initialize
    @sets = []
    @currentSet = 0
    NUMSETS.times { |i|
      @sets[i] = PurifyChamberSet.new
    }
  end

  def currentSet=(value)
    if value >= 0 && value < NUMSETS
      @currentSet = value
    end
  end

  # Number of regular Pokemon in a set
  def setCount(set)
    return @sets[set].length
  end

  def setList(set)
    return [] if set < 0 || set >= NUMSETS

    return @sets[set].list
  end

  def chamberFlow(chamber) # for speeding up purification. German: Fluidum
    return 0 if chamber < 0 || chamber >= NUMSETS

    return @sets[chamber].flow
  end

  def getShadow(chamber)
    return nil if chamber < 0 || chamber >= NUMSETS

    return @sets[chamber].shadow
  end

  def setShadow(chamber, value) # allow only "shadow" Pokemon
    return if chamber < 0 || chamber >= NUMSETS

    @sets[chamber].shadow = value
  end

  def switch(set1, set2)
    return if set1 < 0 || set1 >= NUMSETS
    return if set2 < 0 || set2 >= NUMSETS

    s = @sets[set1]
    @sets[set1] = @sets[set2]
    @sets[set2] = s
  end

  def insertAfter(set, index, value)
    return if set < 0 || set >= NUMSETS

    @sets[set].insertAfter(index, value)
  end

  def insertAt(set, index, value)
    return if set < 0 || set >= NUMSETS

    @sets[set].insertAt(index, value)
  end

  def [](chamber, slot = nil)
    if slot == nil
      return @sets[chamber]
    end
    return nil if chamber < 0 || chamber >= NUMSETS
    return nil if slot < 0 || slot >= SETSIZE

    return @sets[chamber][slot]
  end

  def isPurifiableIgnoreRegular?(set)
    shadow = getShadow(set)
    return false if !shadow
    return false if shadow.heartgauge != 0

    # Define an exception for Lugia
    if shadow.species == :LUGIA
      maxtempo = PurifyChamber.maximumTempo()
      for i in 0...NUMSETS
        return false if @sets[i].tempo != maxtempo
      end
    end
    return true
  end

  def isPurifiable?(set)
    isPurifiableIgnoreRegular?(set) && setCount(set) > 0
  end

  def update # called each step
    for set in 0...NUMSETS
      # If a shadow Pokemon and a regular Pokemon are on the same set
      count = setCount(set)
      if @sets[set].shadow
        if @sets[set].shadow.heartgauge > 0
          flow = self.chamberFlow(set)
          @sets[set].shadow.adjustHeart(-flow)
          if isPurifiable?(set)
            Kernel.pbMessage(_INTL("Your {1} in the Purify Chamber is ready for purification!", @sets[set].shadow.name))
          end
        end
      end
    end
  end

  def debugAddShadow(set, species)
    pkmn = PokeBattle_Pokemon.new(species, 1)
    pkmn.makeShadow
    setShadow(set, pkmn)
  end

  def debugAddNormal(set, species)
    pkmn = PokeBattle_Pokemon.new(species, 1)
    insertAfter(set, setCount(set), pkmn)
  end

  def debugAdd(set, shadow, type1, type2 = nil)
    pkmn = PseudoPokemon.new(shadow, type1, type2 || type1)
    if pkmn.isShadow?
      self.setShadow(set, pkmn)
    else
      self.insertAfter(set, setCount(set), pkmn)
    end
  end
end

class PurifyChamberPC
  def shouldShow?
    return $PokemonGlobal.seenPurifyChamber
  end

  def name
    return _INTL("Purify Chamber")
  end

  def access
    Kernel.pbMessage(_INTL("\\se[accesspc]Accessed the Purify Chamber."))
    pbRelicStone()
    # guys i think the feature that hasn't been maintained in 10 years might be broken, crazy right?
    # gonna deal with this some other time, in the meantime have relic stone access in the PC
    # pbPurifyChamber()
  end
end

PokemonPCList.registerPC(PurifyChamberPC.new)

#####################
#
#  General purpose utilities
#

def pbDrawGauge(bitmap, rect, color, value, maxValue)
  return if !bitmap

  bitmap.fill_rect(rect.x, rect.y, rect.width, rect.height, Color.new(0, 0, 0))
  width = (maxValue <= 0) ? 0 : (rect.width - 4) * value / maxValue
  if rect.width >= 4 && rect.height >= 4
    bitmap.fill_rect(rect.x + 2, rect.y + 2, rect.width - 4, rect.height - 4, Color.new(248, 248, 248))
    bitmap.fill_rect(rect.x + 2, rect.y + 2, width, rect.height - 4, color)
  end
end

def calcPoint(x, y, distance, angle) # angle in degrees
  angle -= (angle / 360.0).floor * 360 # normalize
  angle = (angle / 360.0) * (2 * Math::PI) # convert to radians
  angle = -angle % (2 * Math::PI) # normalize radians
  point = [(Math.cos(angle) * distance), (Math.sin(angle) * distance)]
  point[0] += x
  point[1] += y
  return point
end


#####################


module PurifyChamberHelper
  def self.pbGetPokemon2(chamber, set, position)
    if position == 0
      return chamber.getShadow(set)
    elsif position > 0
      position -= 1
      if position % 2 == 0
        return chamber[set, position / 2]
      else # In between two indices
        return nil
      end
    end

    return nil
  end

  def self.pbGetPokemon(chamber, position)
    if position == 0
      return chamber.getShadow(chamber.currentSet)
    elsif position > 0
      position -= 1
      if position % 2 == 0
        return chamber[chamber.currentSet, position / 2]
      else # In between two indices
        return nil
      end
    end

    return nil
  end

  def self.adjustOnInsert(position)
    if position > 0
      position -= 1
      oldpos = position / 2
      if position % 2 == 0
        return position + 1
      else
        return ((oldpos + 1) * 2) + 1
      end
    end
    return position
  end

  def self.pbSetPokemon(chamber, position, value)
    if position == 0
      chamber.setShadow(chamber.currentSet, value)
    elsif position > 0
      position -= 1
      if position % 2 == 0
        chamber.insertAt(chamber.currentSet, position / 2, value)
      else # In between two indices
        chamber.insertAfter(chamber.currentSet, position / 2, value)
      end
    end
  end
end

class PurifyChamberScreen
  def initialize(scene)
    @scene = scene
    @chamber = $PokemonGlobal.purifyChamber
    #   for j in 0...PurifyChamber::NUMSETS
    #     @chamber.debugAddShadow(j,rand(100)+1)
    #     @chamber[j].shadow.heartgauge=0
    #     for i in 0...PurifyChamber::SETSIZE
    #       @chamber.debugAddNormal(j,rand(100)+1)
    #     end
    #   end
  end

  def pbPlace(pkmn, position)
    return false if !pkmn

    if pkmn.isEgg?
      @scene.pbDisplay(_INTL("Can't place an egg there."))
      return false
    end
    if (position == 0)
      if pkmn.isShadow?
        # Remove from storage and place in set
        oldpkmn = PurifyChamberHelper.pbGetPokemon(@chamber, position)
        if oldpkmn
          @scene.pbShift(position, pkmn)
        else
          @scene.pbPlace(position, pkmn)
        end
        PurifyChamberHelper.pbSetPokemon(@chamber, position, pkmn)
        @scene.pbRefresh()
      else
        @scene.pbDisplay(_INTL("Only a Shadow Pokémon can go there."))
        return false
      end
    elsif (position >= 1)
      if !pkmn.isShadow?
        oldpkmn = PurifyChamberHelper.pbGetPokemon(@chamber, position)
        if oldpkmn
          @scene.pbShift(position, pkmn)
        else
          @scene.pbPlace(position, pkmn)
        end
        PurifyChamberHelper.pbSetPokemon(@chamber, position, pkmn)
        @scene.pbRefresh()
      else
        @scene.pbDisplay(_INTL("Can't place a Shadow Pokemon there."))
        return false
      end
    end
    return true
  end

  def pbPlacePokemon(pos, position)
    return false if !pos

    pkmn = $PokemonStorage[pos[0], pos[1]]
    if pbPlace(pkmn, position)
      $PokemonStorage.pbDelete(pos[0], pos[1])
      return true
    end
    return false
  end

  def pbOnPlace(pkmn)
    set = @chamber.currentSet
    if @chamber.setCount(set) == 0 && @chamber.isPurifiableIgnoreRegular?(set)
      pkmn = @chamber.getShadow(set)
      @scene.pbDisplay(
        _INTL(
          "This {1} is ready to open its heart. However, there must be at least one regular Pokémon in the set to perform a purification ceremony.", pkmn.name
        )
      )
    end
  end

  def pbOpenSetDetail
    chamber = @chamber
    @scene.pbOpenSetDetail(chamber.currentSet)
    selected = nil
    heldpkmn = nil
    loop do
      # Commands
      # array[0]==0 - a position was chosen
      # array[0]==1 - a new set was chosen
      # array[0]==2 - choose Pokemon command
      cmd = @scene.pbSetScreen()
      if cmd[0] == 0
        # Place Pokemon in the set
        curpkmn = PurifyChamberHelper.pbGetPokemon(@chamber, cmd[1])
        if curpkmn || heldpkmn
          commands = [_INTL("MOVE"), _INTL("SUMMARY"), _INTL("WITHDRAW")]
          if curpkmn && heldpkmn
            commands[0] = _INTL("EXCHANGE")
          elsif heldpkmn
            commands[0] = _INTL("PLACE")
          end
          cmdReplace = -1
          cmdRotate = -1
          if !heldpkmn && curpkmn && cmd[1] == 0 &&
             @chamber[@chamber.currentSet].length > 0
            commands[cmdRotate = commands.length] = _INTL("ROTATE")
          end
          if !heldpkmn && curpkmn
            commands[cmdReplace = commands.length] = _INTL("REPLACE")
          end
          commands.push(_INTL("CANCEL"))
          choice = @scene.pbShowCommands(
            _INTL("What shall I do with this {1}?", heldpkmn ? heldpkmn.name : curpkmn.name), commands
          )
          if choice == 0
            if heldpkmn
              if pbPlace(heldpkmn, cmd[1]) # calls place or shift as appropriate
                if !curpkmn
                  pbOnPlace(heldpkmn)
                  @scene.pbPositionHint(PurifyChamberHelper.adjustOnInsert(cmd[1]))
                  heldpkmn = nil # Pokemon was placed
                else
                  heldpkmn = curpkmn # Pokemon was shifted
                end
              end
            else
              @scene.pbMove(cmd[1])
              PurifyChamberHelper.pbSetPokemon(@chamber, cmd[1], nil)
              @scene.pbRefresh()
              heldpkmn = curpkmn
            end
          elsif choice == 1
            @scene.pbSummary(cmd[1], heldpkmn)
          elsif choice == 2
            if pbBoxesFull?
              @scene.pbDisplay(_INTL("All boxes are full."))
            elsif heldpkmn
              @scene.pbWithdraw(cmd[1], heldpkmn)
              $PokemonStorage.pbStoreCaught(heldpkmn)
              heldpkmn = nil
              @scene.pbRefresh()
            else
              # Store and delete Pokemon.
              @scene.pbWithdraw(cmd[1], heldpkmn)
              $PokemonStorage.pbStoreCaught(curpkmn)
              PurifyChamberHelper.pbSetPokemon(@chamber, cmd[1], nil)
              @scene.pbRefresh()
            end
          elsif cmdRotate >= 0 && choice == cmdRotate
            count = @chamber[@chamber.currentSet].length
            nextPos = @chamber[@chamber.currentSet].facing
            if count > 0
              @scene.pbRotate((nextPos + 1) % count)
              @chamber[@chamber.currentSet].facing = (nextPos + 1) % count
              @scene.pbRefresh()
            end
          elsif cmdReplace >= 0 && choice == cmdReplace
            pos = @scene.pbChoosePokemon()
            if pos
              newpkmn = $PokemonStorage[pos[0], pos[1]]
              if newpkmn
                if (newpkmn.isShadow?) != (curpkmn.isShadow?)
                  @scene.pbDisplay(_INTL("That Pokémon can't be placed there."))
                else
                  @scene.pbReplace(cmd, pos)
                  PurifyChamberHelper.pbSetPokemon(@chamber, cmd[1], newpkmn)
                  $PokemonStorage[pos[0], pos[1]] = curpkmn
                  @scene.pbRefresh()
                  pbOnPlace(curpkmn)
                end
              end
            end
          end
        else  # No current Pokemon
          pos = @scene.pbChoosePokemon()
          if pbPlacePokemon(pos, cmd[1])
            curpkmn = PurifyChamberHelper.pbGetPokemon(@chamber, cmd[1])
            pbOnPlace(curpkmn)
            @scene.pbPositionHint(PurifyChamberHelper.adjustOnInsert(cmd[1]))
          end
        end
      elsif cmd[0] == 1 # Change the active set
        @scene.pbChangeSet(cmd[1])
        chamber.currentSet = cmd[1]
      elsif cmd[0] == 2 # Choose a Pokemon
        pos = @scene.pbChoosePokemon()
        pkmn = pos ? $PokemonStorage[pos[0], pos[1]] : nil
        heldpkmn = pkmn if pkmn
      else # cancel
        if heldpkmn
          @scene.pbDisplay(_INTL("You're holding a Pokémon!"))
        else
          if !@scene.pbConfirm(_INTL("Continue editing sets?"))
            break
          end
        end
      end
    end
    if pbCheckPurify()
      @scene.pbDisplay(_INTL("There is a Pokemon that is ready to open its heart!\1"))
      @scene.pbCloseSetDetail()
      pbDoPurify()
      return false
    else
      @scene.pbCloseSetDetail()
      return true
    end
  end

  def pbDisplay(msg)
    @scene.pbDisplay(msg)
  end

  def pbConfirm(msg)
    @scene.pbConfirm(msg)
  end

  def pbRefresh
    @scene.pbRefresh
  end

  def pbCheckPurify
    purifiables = []
    for set in 0...PurifyChamber::NUMSETS
      if @chamber.isPurifiable?(set) # if ready for purification
        purifiables.push(set)
      end
      return purifiables.length > 0
    end
  end

  def pbDoPurify
    purifiables = []
    for set in 0...PurifyChamber::NUMSETS
      if @chamber.isPurifiable?(set) # if ready for purification
        purifiables.push(set)
      end
    end
    for i in 0...purifiables.length
      set = purifiables[i]
      @chamber.currentSet = set
      @scene.pbOpenSet(set)
      @scene.pbPurify()
      pbPurify(@chamber[set].shadow, self)
      pbStorePokemon(@chamber[set].shadow)
      @chamber.setShadow(set, nil) # Remove shadow Pokemon from set
      if (i + 1) != purifiables.length
        @scene.pbDisplay(_INTL("There is another Pokemon that is ready to open its heart!"))
        if !@scene.pbConfirm("Would you like to switch sets?")
          @scene.pbCloseSet()
          break
        end
      end
      @scene.pbCloseSet()
    end
  end

  def pbStartPurify
    chamber = @chamber
    @scene.pbStart(chamber)
    if pbCheckPurify()
      pbDoPurify()
      @scene.pbEnd()
      return
    end
    alreadyopen = false
    @scene.pbOpenSet(chamber.currentSet)
    loop do
      set = @scene.pbChooseSet
      if set < 0
        if !@scene.pbConfirm("Continue viewing holograms?")
          break
        end
      else
        chamber.currentSet = set
        cmd = @scene.pbShowCommands(
          _INTL("What do you want to do?"),
          [
            _INTL("EDIT"), _INTL("SWITCH"), _INTL("CANCEL")
          ]
        )
        if cmd == 0 # edit
          if !pbOpenSetDetail()
            break
          end
        elsif cmd == 1 # switch
          chamber.currentSet = set
          newSet = @scene.pbSwitch(set)
          chamber.switch(set, newSet)
          chamber.currentSet = newSet
          @scene.pbRefresh()
        end
      end
    end
    @scene.pbCloseSet()
    @scene.pbEnd()
  end
end


################################################


class Window_PurifyChamberSets < Window_DrawableCommand
  attr_accessor :switching

  def initialize(chamber, x, y, width, height, viewport = nil)
    @chamber = chamber
    @switching = -1
    super(x, y, width, height, viewport)
  end

  def itemCount
    return PurifyChamber::NUMSETS
  end

  def switching=(value)
    @switching = value; refresh
  end

  def drawItem(index, count, rect)
    textpos = []
    rect = drawCursor(index, rect)
    if index == @switching
      textpos.push([_ISPRINTF("{1:d}", index + 1), rect.x, rect.y, false, Color.new(248, 0, 0), self.shadowColor])
    else
      textpos.push([_ISPRINTF("{1:d}", index + 1), rect.x, rect.y, false, self.baseColor, self.shadowColor])
    end
    if @chamber.setCount(index) > 0
      pbDrawGauge(
        self.contents, Rect.new(rect.x + 16, rect.y + 6, 48, 8),
        Color.new(0, 0, 256), @chamber[index].tempo, PurifyChamber.maximumTempo()
      )
    end
    if @chamber.getShadow(index)
      pbDrawGauge(
        self.contents, Rect.new(rect.x + 16, rect.y + 18, 48, 8),
        Color.new(192, 0, 256), @chamber.getShadow(index).heartgauge,
        PokeBattle_Pokemon::HEARTGAUGESIZE
      )
    end
    pbDrawTextPositions(self.contents, textpos)
  end
end

class DirectFlowDiagram
  def initialize(viewport = nil)
    @points = []
    @angles = []
    @viewport = viewport
    @strength = 0
    @offset = 0
    @x = 306
    @y = 138
    @distance = 96
  end

  # 0=none, 1=weak, 2=strong
  def setFlowStrength(strength)
    @strength = strength
  end

  def visible=(value)
    for point in @points
      point.visible = value
    end
  end

  def dispose
    for point in @points
      point.dispose
    end
  end

  def ensurePoint(j)
    if !@points[j] || @points[j].disposed?
      @points[j] = BitmapSprite.new(8, 8, @viewport)
      @points[j].bitmap.fill_rect(0, 0, 8, 8, Color.new(0, 0, 0))
    end
    @points[j].tone = (@strength == 2) ? Tone.new(232, 232, 248) :
       Tone.new(16, 16, 232)
    @points[j].visible = (@strength != 0)
  end

  def update
    for point in @points
      point.update
      point.visible = false
    end
    j = 0
    i = 0
    while i < @distance
      o = (i + @offset) % @distance
      if o >= 0 && o < @distance
        ensurePoint(j)
        pt = calcPoint(@x, @y, o, @angle)
        @points[j].x = pt[0]
        @points[j].y = pt[1]
        j += 1
      end
      i += (@strength == 2) ? 16 : 32
    end
    @offset += (@strength == 2) ? 3 : 2
    @offset %= @distance
  end

  def color=(value)
    for point in @points
      point.color = value
    end
  end

  def setAngle(angle1)
    @angle = angle1 - (angle1 / 360).floor * 360
  end
end

class FlowDiagram
  def initialize(viewport = nil)
    @points = []
    @angles = []
    @viewport = viewport
    @strength = 0
    @offset = 0
    @x = 306
    @y = 138
    @distance = 96
  end

  # 0=none, 1=weak, 2=strong
  def setFlowStrength(strength)
    @strength = strength
  end

  def visible=(value)
    for point in @points
      point.visible = value
    end
  end

  def dispose
    for point in @points
      point.dispose
    end
  end

  def ensurePoint(j)
    if !@points[j] || @points[j].disposed?
      @points[j] = BitmapSprite.new(8, 8, @viewport)
      @points[j].bitmap.fill_rect(0, 0, 8, 8, Color.new(0, 0, 0))
    end
    @points[j].tone = (@strength == 2) ? Tone.new(232, 232, 248) :
       Tone.new(16, 16, 232)
    @points[j].visible = (@strength != 0)
  end

  def withinRange(angle, startAngle, endAngle)
    if startAngle > endAngle
      return (angle >= startAngle || angle <= endAngle) &&
             (angle >= 0 && angle <= 360)
    else
      return angle >= startAngle && angle <= endAngle
    end
  end

  def update
    for point in @points
      point.update
      point.visible = false
    end
    j = 0
    i = 0
    while i < 360
      angle = (i + @offset) % 360
      if withinRange(angle, @startAngle, @endAngle)
        ensurePoint(j)
        pt = calcPoint(@x, @y, @distance, angle)
        @points[j].x = pt[0]
        @points[j].y = pt[1]
        j += 1
      end
      i += (@strength == 2) ? 10 : 20
    end
    @offset -= (@strength == 2) ? 3 : 2
    @offset %= (360 * 6)
  end

  def color=(value)
    for point in @points
      point.color = value
    end
  end

  def setRange(angle1, angle2)
    @startAngle = angle1 - (angle1 / 360).floor * 360
    @endAngle = angle2 - (angle2 / 360).floor * 360
    if @startAngle == @endAngle && angle1 != angle2
      @startAngle = 0
      @endAngle = 359.99
    end
  end
end

class PurifyChamberSetView < SpriteWrapper
  attr_reader :set
  attr_reader :cursor
  attr_reader :heldpkmn

  def initialize(chamber, set, viewport = nil)
    super(viewport)
    @set = set
    @heldpkmn = nil
    @cursor = -1
    @view = BitmapSprite.new(64, 64, viewport)
    @view.bitmap.fill_rect(8, 8, 48, 48, Color.new(255, 255, 255))
    @view.bitmap.fill_rect(10, 10, 44, 44, Color.new(255, 255, 255, 128))
    @info = BitmapSprite.new(Graphics.width - 112, 48, viewport)
    @flows = []
    @directflow = DirectFlowDiagram.new(viewport)
    @directflow.setAngle(0)
    @directflow.setFlowStrength(1)
    @__sprites = []
    @__sprites[0] = PokemonIconSprite.new(nil, viewport)
    for i in 0...PurifyChamber::SETSIZE * 2
      @__sprites[i + 1] = PokemonIconSprite.new(nil, viewport)
    end
    @__sprites[1 + PurifyChamber::SETSIZE * 2] = PokemonIconSprite.new(nil, viewport)
    @chamber = chamber
    refresh
  end

  def refreshFlows()
    for flow in @flows
      flow.setFlowStrength(0)
    end
    setcount = @chamber.setCount(@set)
    for i in 0...setcount
      if !@flows[i]
        @flows[i] = FlowDiagram.new(self.viewport)
      end
      angle = 360 - (i * 360 / setcount)
      angle += 90 # start at 12 not 3 o'clock
      endAngle = angle - (360 / setcount)
      @flows[i].setRange(endAngle, angle)
      @flows[i].setFlowStrength(@chamber[@set].affinity(i))
    end
  end

  def moveCursor(button)
    points = [@chamber.setCount(@set) * 2, 1].max
    oldcursor = @cursor
    if @cursor == 0 && points > 0
      @cursor = 1 if button == Input::UP
      @cursor = points * 1 / 4 + 1 if button == Input::RIGHT
      @cursor = points * 2 / 4 + 1 if button == Input::DOWN
      @cursor = points * 3 / 4 + 1 if button == Input::LEFT
    elsif @cursor > 0
      pos = @cursor - 1
      if @chamber.setCount(@set) == PurifyChamber::SETSIZE
        points = [points / 2, 1].max
        pos /= 2
      end
      seg = pos * 8 / points
      if seg == 7 || seg == 0
        pos -= 1 if button == Input::LEFT
        pos += 1 if button == Input::RIGHT
        pos = nil if button == Input::DOWN
      elsif seg == 1 || seg == 2
        pos -= 1 if button == Input::UP
        pos += 1 if button == Input::DOWN
        pos = nil if button == Input::LEFT
      elsif seg == 3 || seg == 4
        pos -= 1 if button == Input::RIGHT
        pos += 1 if button == Input::LEFT
        pos = nil if button == Input::UP
      elsif seg == 5 || seg == 6
        pos -= 1 if button == Input::DOWN
        pos += 1 if button == Input::UP
        pos = nil if button == Input::RIGHT
      end
      if pos != nil
        pos -= (pos / points).floor * points # modulus
        pos *= 2 if @chamber.setCount(@set) == PurifyChamber::SETSIZE
        @cursor = pos + 1
      else
        @cursor = 0
      end
    end
    if @cursor != oldcursor
      refresh
    end
  end

  def checkCursor(index)
    if @cursor == index
      @view.x = @__sprites[index].x
      @view.y = @__sprites[index].y
      @view.visible = true
    end
  end

  def refresh
    pkmn = @chamber.getShadow(@set)
    @view.visible = false
    @info.bitmap.fill_rect(0, 0, @info.bitmap.width, @info.bitmap.height, Color.new(0, 248, 0))
    pbSetSmallFont(@info.bitmap)
    textpos = []
    if pkmn
      if pkmn.type2.nil? || pkmn.type1 == pkmn.type2
        textpos.push(
          [_INTL("{1}  Lv{2}  {3}", pkmn.name, pkmn.level, getTypeName(pkmn.type1)), 2, 0, 0, Color.new(248, 248, 248), Color.new(128, 128, 128)]
        )
      else
        textpos.push(
          [_INTL("{1}  Lv{2}  {3}/{4}", pkmn.name, pkmn.level, getTypeName(pkmn.type1), getTypeName(pkmn.type2)), 2, 0, 0, Color.new(248, 248, 248), Color.new(128, 128, 128)]
        )
      end
      textpos.push([_INTL("FLOW"), 2 + @info.bitmap.width / 2, 24, 0, Color.new(248, 248, 248), Color.new(128, 128, 128)])
      # draw heart gauge
      pbDrawGauge(
        @info.bitmap, Rect.new(@info.bitmap.width * 3 / 4, 8, @info.bitmap.width * 1 / 4, 8),
        Color.new(192, 0, 256), pkmn.heartgauge,
        PokeBattle_Pokemon::HEARTGAUGESIZE
      )
      # draw flow gauge
      pbDrawGauge(
        @info.bitmap, Rect.new(@info.bitmap.width * 3 / 4, 24 + 8, @info.bitmap.width * 1 / 4, 8),
        Color.new(0, 0, 248), @chamber.chamberFlow(@set), 6
      )
    end
    if @chamber.setCount(@set) > 0
      textpos.push([_INTL("TEMPO"), 2, 24, 0, Color.new(248, 248, 248), Color.new(128, 128, 128)])
      # draw tempo gauge
      pbDrawGauge(
        @info.bitmap, Rect.new(@info.bitmap.width * 1 / 4, 24 + 8, @info.bitmap.width * 1 / 4, 8),
        Color.new(0, 0, 248), @chamber[@set].tempo,
        PurifyChamber.maximumTempo()
      )
    end
    pbDrawTextPositions(@info.bitmap, textpos)
    @info.x = Graphics.width - @info.bitmap.width
    @info.y = Graphics.height - @info.bitmap.height
    @__sprites[0].pokemon = pkmn
    @__sprites[0].x = 280
    @__sprites[0].y = 96
    @__sprites[0].z = 2
    @directflow.setAngle(angle)
    @directflow.setFlowStrength(0)
    checkCursor(0)
    points = [@chamber.setCount(@set) * 2, 1].max
    setList = @chamber.setList(@set)
    refreshFlows()
    for i in 0...PurifyChamber::SETSIZE * 2
      pkmn = (i % 2 == 1 || i >= points) ? nil : setList[i / 2]
      angle = 360 - (i * 360 / points)
      angle += 90 # start at 12 not 3 o'clock
      if pkmn && @chamber[@set].facing == i / 2
        @directflow.setAngle(angle)
        @directflow.setFlowStrength(@chamber[@set].shadowAffinity)
      end
      point = calcPoint(280, 96, 96, angle)
      @__sprites[i + 1].x = point[0]
      @__sprites[i + 1].y = point[1]
      @__sprites[i + 1].z = 2
      @__sprites[i + 1].pokemon = pkmn
      checkCursor(i + 1)
    end
    @__sprites[1 + PurifyChamber::SETSIZE * 2].pokemon = @heldpkmn
    @__sprites[1 + PurifyChamber::SETSIZE * 2].visible = @view.visible
    @__sprites[1 + PurifyChamber::SETSIZE * 2].x = @view.x
    @__sprites[1 + PurifyChamber::SETSIZE * 2].y = @view.y - 6
    @__sprites[1 + PurifyChamber::SETSIZE * 2].z = 3
  end

  def getCurrent
    return PurifyChamberHelper.pbGetPokemon(@chamber, @cursor)
  end

  def cursor=(value)
    @cursor = value
    refresh
  end

  def heldpkmn=(value)
    @heldpkmn = value
    refresh
  end

  def set=(value)
    @set = value
    refresh
  end

  def visible=(value)
    super
    for sprite in @__sprites
      sprite.visible = value
    end
    for flow in @flows
      flow.visible = value
    end
    @directflow.visible = value
    @view.visible = value
    @info.visible = value
  end

  def color=(value)
    super
    for sprite in @__sprites
      sprite.color = pbSrcOver(sprite.color, value.clone)
    end
    for flow in @flows
      flow.color = value.clone
    end
    @directflow.color = value.clone
    @view.color = value.clone
    @info.color = value.clone
  end

  def update
    super
    for sprite in @__sprites
      sprite.update if sprite
    end
    for flow in @flows
      flow.update
    end
    @directflow.update
    @view.update
    @info.update
  end

  def dispose
    for sprite in @__sprites
      sprite.dispose if sprite
    end
    for flow in @flows
      flow.dispose
    end
    @directflow.dispose
    @view.dispose
    @info.dispose
    @__sprites.clear
    super
  end
end

def pbPurifyChamber()
  $PokemonGlobal.seenPurifyChamber = true
  pbFadeOutIn(99999) {
    scene = PurifyChamberScene.new()
    screen = PurifyChamberScreen.new(scene)
    screen.pbStartPurify()
  }
end

class PurifyChamberScene
  def pbUpdate()
    pbUpdateSpriteHash(@sprites)
  end

  def pbRefresh()
    if @sprites["setview"]
      @sprites["setview"].refresh
      @sprites["setwindow"].refresh
    end
  end

  def pbStart(chamber)
    @chamber = chamber
  end

  def pbEnd()
  end

  def pbOpenSet(set)
    @sprites = {}
    @viewport = Viewport.new(0, 0, Graphics.width, Graphics.height)
    @viewport.z = 99999
    @viewportmsg = Viewport.new(0, 0, Graphics.width, Graphics.height)
    @viewportmsg.z = 99999
    addBackgroundOrColoredPlane(@sprites, "bg", "purifychamberbg", Color.new(0, 0, 0), @viewport)
    @sprites["setwindow"] = Window_PurifyChamberSets.new(
      @chamber, 0, 0, 112, Graphics.height, @viewport
    )
    @sprites["setview"] = PurifyChamberSetView.new(@chamber, set, @viewport)
    @sprites["msgwindow"] = Window_AdvancedTextPokemon.new("")
    @sprites["msgwindow"].viewport = @viewportmsg
    @sprites["msgwindow"].visible = false
    @sprites["setwindow"].index = set
    pbDeactivateWindows(@sprites)
    pbFadeInAndShow(@sprites) { pbUpdate }
  end

  def pbCloseSet()
    pbFadeOutAndHide(@sprites) { pbUpdate }
    pbDisposeSpriteHash(@sprites)
    @viewport.dispose
    @viewportmsg.dispose
  end

  def pbOpenSetDetail(set)
    @sprites["setwindow"].index = set
    @sprites["setview"].set = set
    @sprites["setview"].cursor = 0
  end

  def pbCloseSetDetail()
  end

  def pbPurify()
    pbRefresh
  end

  def pbMove(pos)
    @sprites["setview"].heldpkmn = @sprites["setview"].getCurrent()
    pbRefresh
  end

  def pbShift(pos, heldpoke)
    @sprites["setview"].heldpkmn = @sprites["setview"].getCurrent()
    pbRefresh
  end

  def pbPlace(pos, heldpoke)
    @sprites["setview"].heldpkmn = nil
    pbRefresh
  end

  def pbReplace(pos, storagePos)
    @sprites["setview"].heldpkmn = nil
    pbRefresh
  end

  def pbRotate(facing)
  end

  def pbWithdraw(pos, heldpoke)
    @sprites["setview"].heldpkmn = nil
    pbRefresh
  end

  def pbDisplay(msg)
    UIHelper.pbDisplay(@sprites["msgwindow"], msg, false) { pbUpdate }
  end

  def pbConfirm(msg)
    UIHelper.pbConfirm(@sprites["msgwindow"], msg) { pbUpdate }
  end

  def pbShowCommands(msg, commands)
    UIHelper.pbShowCommands(@sprites["msgwindow"], msg, commands) { pbUpdate }
  end

  def pbSetScreen()
    pbDeactivateWindows(@sprites) {
      loop do
        Graphics.update; Input.update; pbUpdate
        btn = 0
        btn = Input::DOWN if Input.repeat?(Input::DOWN)
        btn = Input::UP if Input.repeat?(Input::UP)
        btn = Input::RIGHT if Input.repeat?(Input::RIGHT)
        btn = Input::LEFT if Input.repeat?(Input::LEFT)
        if btn != 0
          pbPlayCursorSE()
          @sprites["setview"].moveCursor(btn)
        end
        if Input.repeat?(Input::L)
          nextset = (@sprites["setview"].set == 0) ? PurifyChamber::NUMSETS - 1 : @sprites["setview"].set - 1
          pbPlayCursorSE()
          return [1, nextset]
        end
        if Input.repeat?(Input::R)
          nextset = (@sprites["setview"].set == PurifyChamber::NUMSETS - 1) ? 0 : @sprites["setview"].set + 1
          pbPlayCursorSE()
          return [1, nextset]
        end
        if Input.trigger?(Input::C)
          pbPlayDecisionSE()
          return [0, @sprites["setview"].cursor]
        end
        if Input.trigger?(Input::B)
          pbPlayCancelSE()
          return [3, 0]
        end
      end
    }
  end

  def pbChooseSet()
    pbActivateWindow(@sprites, "setwindow") {
      oldindex = @sprites["setwindow"].index
      loop do
        if oldindex != @sprites["setwindow"].index
          oldindex = @sprites["setwindow"].index
          @sprites["setview"].set = oldindex
        end
        Graphics.update; Input.update; pbUpdate
        if Input.trigger?(Input::C)
          pbPlayDecisionSE()
          return @sprites["setwindow"].index
        end
        if Input.trigger?(Input::B)
          pbPlayCancelSE()
          return -1
        end
      end
    }
  end

  def pbSwitch(set)
    @sprites["setwindow"].switching = set
    ret = pbChooseSet()
    @sprites["setwindow"].switching = -1
    return ret < 0 ? set : ret
  end

  def pbSummary(pos, heldpkmn)
    if heldpkmn
      oldsprites = pbFadeOutAndHide(@sprites)
      scene = PokemonSummaryScene.new
      screen = PokemonSummary.new(scene)
      screen.pbStartScreen([heldpkmn], 0)
      pbFadeInAndShow(@sprites, oldsprites)
      return
    end
    party = []
    indexes = []
    startindex = 0
    set = @sprites["setview"].set
    for i in 0...@chamber.setCount(set) * 2
      p = PurifyChamberHelper.pbGetPokemon2(@chamber, set, i)
      if p
        startindex = party.length if i == pos
        party.push(p)
        indexes.push(i)
      end
    end
    return if party.length == 0

    oldsprites = pbFadeOutAndHide(@sprites)
    scene = PokemonSummaryScene.new
    screen = PokemonSummary.new(scene)
    selection = screen.pbStartScreen(party, startindex)
    @sprites["setview"].cursor = indexes[selection]
    pbFadeInAndShow(@sprites, oldsprites)
  end

  def pbPositionHint(pos)
    @sprites["setview"].cursor = pos
    pbRefresh
  end

  def pbChangeSet(set)
    @sprites["setview"].set = set
    @sprites["setwindow"].index = set
    @sprites["setwindow"].refresh
    pbRefresh
  end

  def pbChoosePokemon()
    visible = pbFadeOutAndHide(@sprites)
    scene = PokemonStorageScene.new()
    screen = PokemonStorageScreen.new(scene, $PokemonStorage)
    pos = screen.pbChoosePokemon()
    pbRefresh
    pbFadeInAndShow(@sprites, visible)
    return pos
  end
end
