require 'whirled_peas/utils/formatted_string'

module WhirledPeas
  module Graphics
    # Canvas represent the area of the screen a painter can paint on.
    class Canvas
      attr_reader :left, :top, :width, :height, :start_left, :start_top

      def self.unwritable
        new(-1, -1, 0, 0, -1, -1)
      end

      def initialize(left, top, width, height, start_left, start_top)
        @left = left
        @top = top
        @width = width
        @height = height
        @start_left = start_left
        @start_top = start_top
      end

      def writable?
        width > 0 || height > 0
      end

      def child(start_left, start_top, child_width, child_height)
        child_left = start_left
        child_top = start_top
        if child_left >= left + width
          self.class.unwritable
        elsif child_left + child_width <= left
          self.class.unwritable
        elsif child_top >= top + height
          self.class.unwritable
        elsif child_top + child_height <= top
          self.class.unwritable
        else
          if child_left < left
            child_width -= left - child_left
            child_left = left
          end
          child_width = [width - (child_left - left), child_width].min
          if child_top < top
            child_height -= top - child_top
            child_top = top
          end
          child_height = [height - (child_top - top), child_height].min
          self.class.new(
            child_left,
            child_top,
            child_width,
            child_height,
            start_left,
            start_top
          )
        end
      end

      # Yields a single line of formatted characters positioned on the canvas,
      # verifying only characters within the canvas are included.
      def stroke(stroke_left, stroke_top, raw, formatting=[], &block)
        if stroke_left >= left + width
          # The stroke starts to the right of the canvas
          fstring = Utils::FormattedString.blank
        elsif stroke_left + raw.length <= left
          # The stroke ends to the left of the canvas
          fstring = Utils::FormattedString.blank
        elsif stroke_top < top
          # The stroke is above the canvas
          fstring = Utils::FormattedString.blank
        elsif stroke_top >= top + height
          # The stroke is below the canvas
          fstring = Utils::FormattedString.blank
        else
          # In this section, we know that at least part of the stroke should be visible
          # on the canvas. Chop off parts of the raw string that aren't within the
          # canvas boundary and ensure the stroke start position is also within the
          # canvas boundary

          # If the stroke starts to the left of the canvas, set the start index to the
          # first value that will be on the canvas, then update stroke_left to be on
          # the canvas
          start_index = stroke_left < left ? left - stroke_left : 0
          stroke_left = left if stroke_left <= left

          # Determine how many characters from the stroke will fit on the canvas
          visible_length = [raw.length, width - (stroke_left - left)].min
          end_index = start_index + visible_length - 1
          fstring = Utils::FormattedString.new(raw[start_index..end_index], formatting)
        end
        yield stroke_left, stroke_top, fstring
      end

      def hash
        [left, top, width, height].hash
      end

      def ==(other)
        other.is_a?(self.class) && hash == other.hash
      end

      def inspect
        "Canvas(left=#{left}, top=#{top}, width=#{width}, height=#{height})"
      end
    end
  end
end