# 二维向量
class Vector
 
  # 可以进行枚举,参见#each
  include(Enumerable)
 
  attr_accessor :x # x方向上的分量
  attr_accessor :y # y方向上的分量
 
  # 初始化
  # 无参数时:(0, 0)
  # 有一个参数r时:(r, r)
  # 有两个参数x, y时:(x, y)
  def initialize(*args)
    case args.size
    when 0
      @x = 0
      @y = 0
    when 1
      @x = args[0]
      @y = args[0]
    when 2
      @x = args[0]
      @y = args[1]
    end
  end
 
  def +@
    self
  end
 
  # 反向量
  def -@
    Vector.new(-@x, -@y)
  end
 
  # 加法
  def +(other)
    Vector.new(@x + other.x, @y + other.y)
  end
 
  # 减法
  def -(other)
    self + -other
  end
 
  # 点乘或数乘运算
  def *(other)
    case other
    when Numeric
      Vector.new(@x * other, @y * other)
    when Vector
      @x * other.x + @y * other.y
    end
  end
 
  # 点积
  def dot(other)
    @x * other.x + @y * other.y
  end
 
  # 叉积
  def cross(other)
    self.x * other.y - other.x * self.y
  end
 
  # 斜率
  def slope
    x =! 0 ? y / x : Float::Infinity
  end
 
  # 模的平方
  def square_length
    self * self
  end
 
  # 模
  def length
    Math.sqrt(square_length)
  end
 
  # 超出范围
  def is_out_of?(other)
    @x.abs > other.x.abs || @y.abs > other.y.abs
  end
 
  # 对x和y方向的分量分别取绝对值
  def abs
    Vector.new(@x.abs, @y.abs)
  end
 
  # 切向
  def normal
    Vector.new(-@y, @x)
  end
 
  # 枚举,先对x计算,后对y计算
  def each
    yield @x
    yield @y
  end
 
  def inspect
    "(#{@x}, #{@y})"
  end
end
 
module Math
 
  # 获得两点之间由某个比例决定的分点
  def self.definite_proportion(point1, point2, proportion)
    point1 * (1 - proportion) + point2 * proportion
  end
end
 
class Rect
 
  # 初始化
  # 除了原先的参数列表,支持由两个向量来决定其位置大小
  # 如果用向量表示,第一个向量是position,第二个向量是size
  alias ulysses201811181949_initialize initialize
  def initialize(*args)
    args = case args.size
    when 0, 4
      args
    when 2
      [args[0].x, args[0].y, args[1].x, args[1].y]
    end
    ulysses201811181949_initialize(*args)
  end
 
  def position
    Vector.new(x, y)
  end
 
  def position=(position)
    self.x = position.x
    self.y = position.y
  end
 
  def size
    Vector.new(width, height)
  end
 
  def size=(size)
    self.width = size.x
    self.height = size.y
  end
end
 
module Kernel
 
  # 令变量从from到to按照step进行线性变化并传递到块中
  def do_by_step(from, to, step)
    if block_given?
      i = from
      until i >= to
        yield i
        i += step
      end
    else
      to_enum(__method__, from, to, step)
    end
  end
 
  def gets(*args)
    $stdin.gets(*args)
  end
 
  def Vector(*args)
    Vector.new(*args)
  end
end
 
class Bitmap
 
  # 初始化
  # 支持向量表示
  alias ulysses201811181708_initialize initialize
  def initialize(*args)
    args = if args.size == 2 || args[0].is_a?(String)
      args
    elsif args.size == 1
      [args[0].x, args[0].y]
    else
      [0, 0]
    end
    ulysses201811181708_initialize(*args)
  end
 
  # 填充一个圆
  def fill_circle(center, radius, color)
    i_max = (radius * 707) / 1000 + 1
    square_distance_max = radius * radius + radius / 2
    x = radius
    fill_rect(center.x - radius, center.y, 2 * radius, 1, color)
    for i in 1..i_max
      if Vector.new(center.x, i).square_length > square_distance_max
        if x > i_max
          fill_rect(center.x - i + 1, center.y + x, 2 * (i - 1), 1, color)
          fill_rect(center.x - i + 1, center.y - x, 2 * (i - 1), 1, color)
        end
        x -= 1
      end
      fill_rect(center.x - x, center.y + i, 2 * x, 1, color)
      fill_rect(center.x - x, center.y - i, 2 * x, 1, color)
    end
  end
 
  # 用一条1像素宽的线段联结两点
  def draw_line(point1, point2, color)
    difference = point2 - point1
    abs_difference = difference.abs
    total_step = abs_difference.max
    delta = difference * (1.0 / total_step)
    vector = point1
    (total_step.to_i + 1).times do
      set_pixel(vector, color)
      vector += delta
    end
  end
 
  alias ulysses201811182036_set_pixel set_pixel
  def set_pixel(*args)
    args = case args.size
    when 3
      args
    when 2
      [args[0].x, args[0].y, args[1]]
    end
    ulysses201811182036_set_pixel(*args)
  end
 
  def text_vector(text)
    text_size(text).size
  end
 
  def size
    Vector.new(width, height)
  end
 
  def size=(size)
    self.rect.size = size
  end
 
  alias ulysses201811181952_draw_text draw_text
  def draw_text(*args)
    args = case args[0]
    when Numeric, Rect
      args
    when Vector
      a = [args[0].x, args[0].y, args[1].x, args[1].y, args[2]]
      a.push(args[3]) if args.size >= 4
      a
    end
    ulysses201811181952_draw_text(*args)
  end
end
 
class Sprite
 
  def position=(position)
    self.x = position.x
    self.y = position.y
  end
end
 
class Viewport
 
  # 让add_fast_layer接受默认参数blend_type = 0
  alias ulysses201812021851_add_fast_layer add_fast_layer
  def add_fast_layer(z, blend_type = 0)
    ulysses201812021851_add_fast_layer(z, blend_type)
  end
end
 
# 重写了Input模块
# 支持全键盘和鼠标按键
# 如果不需要判断鼠标位置的话,可以不update
module Input
 
  REPEAT_TIME = 4 # 当key被按超过此数时,repeat?(key) == true
  GetKeyState = Win32API.new("user32", "GetAsyncKeyState", ['I'], 'I')
 
  @key_states = {}
  @key_repeat_times = {}
 
  def self.press?(key)
    key < 0x08 ? Mouse.press?(key) : GetKeyState.call(key) != 0
  end
 
  def self.repeat?(key)
    if press?(key)
      unless @key_repeat_times[key]
        @key_repeat_times[key] = 0
        return true
      end
      @key_repeat_times[key] += 1
    else
      @key_repeat_times[key] = nil
      @key_states[key] = 0
    end
    if !@key_repeat_times[key].nil? && @key_repeat_times[key] > REPEAT_TIME
      @key_repeat_times[key] = 0
      return true
    end
    false
  end
 
  def self.trigger?(key)
    if press?(key)
      return false if @key_states[key] == 1
      @key_states[key] = 1
      true
    else
      @key_states[key] = 0
      false
    end
  end
 
  def self.update
    Mouse.update
  end
end
 
class << Graphics
 
  def size
    Vector.new(width, height)
  end
 
  alias ulysses201811251830_resize_screen resize_screen
  def resize_screen(*args)
    case args.size
    when 2
      ulysses201811251830_resize_screen(*args)
    when 1
      ulysses201811251830_resize_screen(args[0].x, args[0].y)
    end
  end
end
 
Graphics.resize_screen(1024, 768) # 更改分辨率
 
module Mouse
 
  def self.position
    Vector.new(x, y)
  end
end
 
# 封装为模块的场景
module Scene
 
  # 绘制控制点的参数
  POINTS_RADIUS = 3
  POINTS_COLOR = Color.new(255, 255, 255, 255)
  POINTS_Z = 10
  # 绘制曲线的参数
  CURVE_COLOR = Color.new(255, 255, 255, 255)
  CURVE_Z = 0
  # 绘制线段的参数
  LINES_COLOR = Color.new(255, 255, 255, 127)
  LINES_Z = -10
  # 用于调整绘制的范围和精度
  T_BEGINNING = 0.0
  T_END = 1.0
  T_STEP = 0.01
  UPDATE_STEPS = 5
  TEMP_BITMAP = Bitmap.new(32, 32) # 临时位图,用于获取文字大小
  # 按键
  CREATE_CONTROL_POINT_KEY = 0x01 # left
  DELETE_CONTROL_POINT_KEY = 0x02 # right
  FAST_MODE_KEY = 0x41 # A
  LINES_VISIBLE_KEY = 0x42 # B
  CONTROL_POINTS_VISIBLE_KEY = 0x43 # C
 
  # 显示端口
  @viewport = Viewport.new
  @viewport.add_fast_layer(POINTS_Z)
  # 曲线
  @curve_sprite = Sprite.new(@viewport)
  @curve_sprite.bitmap = Bitmap.new(Graphics.size)
  @curve_sprite.z = CURVE_Z
  # 线段
  @lines_sprite = Sprite.new(@viewport)
  @lines_sprite.bitmap = Bitmap.new(Graphics.size)
  @lines_sprite.z = LINES_Z
  # 控制点
  @control_points = []
  @control_point_sprites = []
  # 模式控制
  @lines_visible = true
  @control_points_visible = true
  @fast_mode = false
  # 阻断刷新控制
  @covering_drawing = 0
 
  # 创建控制点
  def self.create_control_point(point)
    sprite = Sprite.new(@viewport)
    sprite.visible = @control_points_visible
    radius_vector = Vector.new(POINTS_RADIUS)
    text_vector = TEMP_BITMAP.text_vector(@control_points.size)
    radius_text_vector = radius_vector + text_vector
    sprite.bitmap = Bitmap.new(radius_text_vector + radius_vector)
    sprite.bitmap.font.color = POINTS_COLOR
    x_flip = (point + radius_text_vector).x > Graphics.width
    y_flip = (point + radius_text_vector).y > Graphics.height
    excursion = Vector.new
    excursion.x = x_flip ? radius_text_vector.x : radius_vector.x
    excursion.y = y_flip ? radius_text_vector.y : radius_vector.y
    sprite.position = point - excursion
    sprite.bitmap.fill_circle(excursion, POINTS_RADIUS, POINTS_COLOR)
    text_position = Vector.new
    text_position.x = x_flip ? 0 : radius_vector.x * 2
    text_position.y = y_flip ? 0 : radius_vector.y * 2
    sprite.bitmap.draw_text(text_position, text_vector, @control_points.size)
    sprite.z = POINTS_Z
    @control_point_sprites.push(sprite)
    @control_points.push(point)
    refresh_graph
    point
  end
 
  # 删除上一个控制点
  def self.delete_control_point
    return if @control_points.empty?
    point = @control_points.pop
    sprite = @control_point_sprites.pop
    sprite.bitmap.dispose
    sprite.dispose
    refresh_graph
    point
  end
 
  # 刷新图形
  # 该方法在非快速模式下会阻塞,但update不会停且可以被打断
  def self.refresh_graph
    @covering_drawing += 1
    @curve_sprite.bitmap.clear
    return if @control_points.empty?
    graph_points = []
    steps_count = 0
    do_by_step(T_BEGINNING, T_END, T_STEP) do |t|
      will_update = !@fast_mode && (steps_count += 1) >= UPDATE_STEPS
      graph_points.push(next_graph_point(t, will_update))
      if graph_points.size > 1
        @curve_sprite.bitmap.draw_line(*graph_points.last(2), CURVE_COLOR)
      end
      if will_update
        steps_count = 0
        last_covering_drawing = @covering_drawing
        update
        return if @covering_drawing > last_covering_drawing
      end
    end
    connect_control_points_with_lines
  end
 
  # 根据t返回曲线上的点坐标
  # 若@lines_visible && will_update则会绘制线段
  def self.next_graph_point(t, will_update = false)
    is_drawing = @lines_visible && will_update
    @lines_sprite.bitmap.clear if is_drawing
    sequences = [@control_points]
    (@control_points.size - 1).times do |i|
      last_sequence = sequences.last
      sequences.push([])
      (last_sequence.size - 1).times do |j|
        if is_drawing
          @lines_sprite.bitmap.draw_line(*last_sequence[j, 2], LINES_COLOR)
        end
        sequences.last.push(Math.definite_proportion(*last_sequence[j, 2], t))
      end
    end
    sequences.last.first
  end
 
  # 用线段将控制点联结
  def self.connect_control_points_with_lines
    @lines_sprite.bitmap.clear
    (@control_points.size - 1).times do |i|
      @lines_sprite.bitmap.draw_line(*@control_points[i, 2], LINES_COLOR)
    end
    nil
  end
 
  # 切换线段可见
  def self.toggle_lines_visible
    @lines_visible = !@lines_visible
    @lines_sprite.visible = @lines_visible
  end
 
  # 切换控制点可见
  def self.toggle_points_visible
    @control_points_visible = !@control_points_visible
    @control_point_sprites.each do |sprite|
      sprite.visible = @control_points_visible
    end
  end
 
  # 切换快速模式
  def self.toggle_fast_mode
    @fast_mode = !@fast_mode
  end
 
  # 基础更新,必须每帧调用
  def self.update_basic
    Graphics.update
    Input.update
  end
 
  # 更新,需每帧调用
  def self.update
    update_basic
    if Input.trigger?(CREATE_CONTROL_POINT_KEY)
      create_control_point(Mouse.position)
    elsif Input.trigger?(DELETE_CONTROL_POINT_KEY)
      delete_control_point
    elsif Input.trigger?(FAST_MODE_KEY)
      toggle_fast_mode
    elsif Input.trigger?(LINES_VISIBLE_KEY)
      toggle_lines_visible
    elsif Input.trigger?(CONTROL_POINTS_VISIBLE_KEY)
      toggle_points_visible
    end
  end
 
  # 简单的irb
  def self.simple_irb_start
    Thread.start do
      ans = nil
      loop { puts("=> #{(ans = eval(gets)).inspect}") rescue puts($!.message) }
    end
  end
 
  # 主进程
  def self.main
    simple_irb_start
    loop { update }
  end
end
 
# 入口
Scene.main