require "falling_blocks/piece"
require "falling_blocks/field"

module FallingBlocks
  
  class Model
    
    attr_reader :state
    
    attr_reader :score
    def level
      [@lines / 5 + 1, @init_level].max
    end
    attr_reader :lines
    attr_reader :field
    
    attr_reader :falling_piece
    def falling_piece_x
      @falling_piece_x_100.quo(100)
    end
    def falling_piece_y
      @falling_piece_y_100.quo(100)
    end
    attr_reader :falling_piece_angle
    
    attr_reader :next_piece
    
    def initialize(init_level)
      @init_level = init_level
      start
    end
    
    def start
      @state = :start
      @score = 0
      @lines = 0
      @field = Field.new
      go_next_piece
    end
    
    def start_playing
      @state = :playing
    end
    
    def pause
      @state = :pause if @state == :playing
    end
    
    def resume
      @state = :playing if @state == :pause
    end
    
    def try_move(direction)
      x_100 = @falling_piece_x_100
      case direction
      when :left
        x_100 -= 100
      when :right
        x_100 += 100
      end
      x = x_100.quo(100).ceil
      y = @falling_piece_y_100.quo(100).ceil
      angle = @falling_piece_angle
      unless field.conflict?(@falling_piece, x, y, angle)
        @falling_piece_x_100 = x_100
      end
    end
    
    def try_rotate(direction)
      angle = @falling_piece_angle
      case direction
      when :left
        angle = (angle + 3) % 4
      when :right
        angle = (angle + 1) % 4
      end
      x = @falling_piece_x_100.quo(100).ceil
      y = @falling_piece_y_100.quo(100).ceil
      unless field.conflict?(@falling_piece, x, y, angle)
        @falling_piece_angle = angle
      end
    end
    
    def try_fall(down_key = false)
      dy_100 = level * 5
      dy_100 = [dy_100, 100].max if down_key
      y_100 = @falling_piece_y_100
      x = @falling_piece_x_100.quo(100).ceil
      angle = @falling_piece_angle
      loop do
        y_100 += [100, dy_100].min
        dy_100 -= [100, dy_100].min
        y = y_100.quo(100).ceil
        if field.conflict?(@falling_piece, x, y, angle)
          @falling_piece_y_100 = (y - 1) * 100
          break
        end
        if dy_100 <= 0
          @falling_piece_y_100 = y_100
          break
        end
      end
    end
    
    def add_score(d_score)
      @score += d_score
    end
    
    def falling_piece_landing?
      x = @falling_piece_x_100.quo(100).ceil
      y = @falling_piece_y_100.quo(100).floor
      angle = @falling_piece_angle
      field.conflict?(@falling_piece, x, y + 1, angle)
    end
    
    def do_landing
      x = @falling_piece_x_100 / 100
      y = @falling_piece_y_100 / 100
      angle = @falling_piece_angle
      if @field.add_piece(@falling_piece, x, y, angle)
        go_next_piece if @field.flashing_lines.empty?
      else
        @state = :gameover
      end
    end
    
    def flashing?
      not @field.flashing_lines.empty?
    end
    
    def finish_flashing
      lines_size = @field.flashing_lines.size
      @score += (case lines_size
                 when 1; 100
                 when 2; 300
                 when 3; 900
                 when 4; 2700
                 end) * level
      @lines += lines_size
      @field.flash
      go_next_piece
    end
    
    private
    
    def go_next_piece
      @falling_piece = @next_piece || Piece[rand(Piece.count)]
      @next_piece = Piece[rand(Piece.count)]
      @falling_piece_x_100 = (field.width - @falling_piece.width) / 2 * 100
      @falling_piece_y_100 = 0
      @falling_piece_angle = 0
    end
    
  end
  
end
