require 'psd/renderer/cairo_helpers'

class PSD
  class Renderer
    class VectorShape
      include CairoHelpers

      def self.can_render?(canvas)
        canvas.opts[:render_vectors] && !canvas.node.vector_mask.nil?
      end

      DPI = 72.0.freeze

      def initialize(canvas)
        @canvas = canvas
        @node = @canvas.node
        @path = @node.vector_mask.paths.map(&:to_hash)

        @stroke_data = @node.vector_stroke ? @node.vector_stroke.data : {}
        @fill_data = @node.vector_stroke_content ? @node.vector_stroke_content.data : {}

        @paths = []
      end

      def render!
        PSD.logger.debug "Beginning vector render for #{@node.name}"

        find_points
        render_shapes
      end

      private

      def find_points
        PSD.logger.debug "Formatting vector points..."

        cur_path = nil
        @path.each do |data|
          next if [6, 7, 8].include? data[:record_type]
          
          if [0, 3].include? data[:record_type]
            @paths << cur_path
            cur_path = []
            next
          end

          cur_path << data.tap do |d|
            if [1, 2, 4, 5].include? data[:record_type]
              [:preceding, :anchor, :leaving].each do |type|
                d[type][:horiz] = (d[type][:horiz] * horiz_factor) - @node.left
                d[type][:vert]  = (d[type][:vert] * vert_factor) - @node.top
              end
            end
          end
        end

        @paths << cur_path
        @paths.compact!

        PSD.logger.debug "Vector shape has #{@paths.size} path(s)"
      end

      # TODO: stroke alignment
      # Right now we assume the stroke style is always a overlap stroke.
      def render_shapes
        PSD.logger.debug "Rendering #{@paths.size} vector paths with cairo"

        cairo_image_surface(@canvas.width + stroke_size, @canvas.height + stroke_size) do |cr|
          cr.set_fill_rule Cairo::FILL_RULE_EVEN_ODD
          cr.set_line_join stroke_join
          cr.set_line_cap stroke_cap

          cr.translate stroke_size / 2.0, stroke_size / 2.0

          @paths.each do |path|
            cr.move_to path[0][:anchor][:horiz], path[0][:anchor][:vert]

            path.size.times do |i|
              point_a = path[i]
              point_b = path[i+1] || path[0]

              cr.curve_to(
                point_a[:leaving][:horiz],
                point_a[:leaving][:vert],
                point_b[:preceding][:horiz],
                point_b[:preceding][:vert],
                point_b[:anchor][:horiz],
                point_b[:anchor][:vert]
              )
            end

            cr.close_path if path.last[:closed]
          end

          cr.set_source_rgba fill_color
          cr.fill_preserve

          if has_stroke?
            cr.set_source_rgba stroke_color
            cr.set_line_width stroke_size
            cr.stroke
          end
        end
      end

      # For debugging purposes only
      def draw_debug(canvas)
        @paths.each do |path|
          path.each do |point|
            canvas.circle(point[:anchor][:horiz].to_i, point[:anchor][:vert].to_i, 3, ChunkyPNG::Color::BLACK, ChunkyPNG::Color::BLACK)
            [:leaving, :preceding].each do |type|
              canvas.circle(point[type][:horiz].to_i, point[type][:vert].to_i, 3, ChunkyPNG::Color.rgb(255, 0, 0), ChunkyPNG::Color.rgb(255, 0, 0))
              canvas.line(
                point[:anchor][:horiz].to_i, point[:anchor][:vert].to_i,
                point[type][:horiz].to_i, point[type][:vert].to_i,
                ChunkyPNG::Color::BLACK
              )
            end
          end
        end
      end

      def apply_to_canvas(output)
        # draw_debug(output)
        output.resample_nearest_neighbor!(@canvas.width, @canvas.height)
        @canvas.canvas.compose!(output, 0, 0)
      end

      def formatted_points
        @formatted_points ||= @curve_points.map(&:to_a)
      end

      def horiz_factor
        @horiz_factor ||= @node.root.width.to_f
      end

      def vert_factor
        @vert_factor ||= @node.root.height.to_f
      end

      def stroke_color
        @stroke_color ||= (
          if @stroke_data['strokeEnabled']
            colors = @stroke_data['strokeStyleContent']['Clr ']
            [
              colors['Rd  '] / 255.0,
              colors['Grn '] / 255.0,
              colors['Bl  '] / 255.0,
              @stroke_data['strokeStyleOpacity'][:value] / 100.0
            ]
          else
            [0.0, 0.0, 0.0, 0.0]
          end
        )
      end

      def fill_color
        @fill_color ||= (
          overlay = PSD::LayerStyles::ColorOverlay.for_canvas(@canvas)
          
          if overlay
            [
              overlay.r / 255.0,
              overlay.g / 255.0,
              overlay.b / 255.0,
              overlay.a / 255.0
            ]
          elsif @stroke_data['fillEnabled']
            colors = @fill_data['Clr ']
            [
              colors['Rd  '] / 255.0,
              colors['Grn '] / 255.0,
              colors['Bl  '] / 255.0,
              @stroke_data['strokeStyleOpacity'][:value] / 100.0
            ]
          elsif !@node.solid_color.nil?
            [
              @node.solid_color.r / 255.0,
              @node.solid_color.g / 255.0,
              @node.solid_color.b / 255.0,
              1.0
            ]
          else
            [0.0, 0.0, 0.0, 0.0]
          end
        )
      end

      def stroke_size
        @stroke_size ||= (
          if @stroke_data['strokeStyleLineWidth']
            value = @stroke_data['strokeStyleLineWidth'][:value]

            # Convert to pixels
            if @stroke_data['strokeStyleLineWidth'][:id] == '#Pnt'
              value = @stroke_data['strokeStyleResolution'] * value / 72.27
            end

            value.to_i
          else
            0
          end
        )
      end

      def stroke_cap
        @stroke_cap ||= (
          if @stroke_data['strokeStyleLineCapType']
            case @stroke_data['strokeStyleLineCapType']
            when 'strokeStyleButtCap' then Cairo::LINE_CAP_BUTT
            when 'strokeStyleRoundCap' then Cairo::LINE_CAP_ROUND
            when 'strokeStyleSquareCap' then Cairo::LINE_CAP_SQUARE
            end
          else
            Cairo::LINE_CAP_BUTT
          end
        )
      end

      def stroke_join
        @stroke_join ||= (
          if @stroke_data['strokeStyleLineJoinType']
            case @stroke_data['strokeStyleLineJoinType']
            when 'strokeStyleMiterJoin' then Cairo::LINE_JOIN_MITER
            when 'strokeStyleRoundJoin' then Cairo::LINE_JOIN_ROUND
            when 'strokeStyleBevelJoin' then Cairo::LINE_JOIN_BEVEL
            end
          else
            Cairo::LINE_JOIN_MITER
          end
        )
      end

      def has_stroke?
        stroke_size > 0
      end
    end
  end
end