class PSD
  class Renderer
    class Canvas
      extend Forwardable

      attr_reader :canvas, :node, :opts, :width, :height, :opacity, :fill_opacity

      def_delegators :node, :top, :right, :bottom, :left
      def_delegators :canvas, :[], :[]=, :get_pixel, :set_pixel

      def initialize(node, width = nil, height = nil, opts = {})
        @node = node
        @opts = opts
        @pixel_data = @node.group? ? [] : @node.image.pixel_data
        
        @width = (width || @node.width).to_i
        @height = (height || @node.height).to_i

        @opacity = @node.opacity.to_f
        @fill_opacity = @node.fill_opacity.to_f

        initialize_canvas
      end

      def paint_to(base)
        PSD.logger.debug "Painting #{node.name} to #{base.node.debug_name}"

        apply_masks
        apply_clipping_mask
        apply_layer_styles
        apply_layer_opacity

        compose_pixels(base)
      end

      def canvas=(canvas)
        @canvas = canvas
        @width = @canvas.width
        @height = @canvas.height
      end

      def method_missing(method, *args, &block)
        @canvas.send(method, *args, &block)
      end

      private

      def initialize_canvas
        PSD.logger.debug "Initializing canvas for #{node.debug_name}; color = #{ChunkyPNG::Color.to_truecolor_alpha_bytes(fill_color)}"

        @canvas = ChunkyPNG::Canvas.new(@width, @height, fill_color)
        return if @node.group? || has_fill? || @opts[:base]

        # Sorry, ChunkyPNG.
        @canvas.send(:replace_canvas!, width, height, @pixel_data)
      ensure
        # This can now be referenced by @canvas.pixels
        @pixel_data = nil
      end

      def has_fill?
        !@opts[:base] && @node.layer? && @node.solid_color
      end

      def fill_color
        if has_fill?
          @node.solid_color.color
        else
          ChunkyPNG::Color::TRANSPARENT
        end
      end

      def apply_masks
        ([@node] + @node.ancestors).each do |n|
          next unless n.raster_mask?
          break if n.group? && !n.passthru_blending?

          if n.layer?
            PSD.logger.debug "Applying raster mask to #{@node.name}"
            Mask.new(self).apply!
          else
            PSD.logger.debug "Applying raster mask to #{@node.name} from #{n.name}"
            Mask.new(self, n).apply!
          end
        end
      end

      def apply_clipping_mask
        return unless @node.clipped?
        ClippingMask.new(self).apply!
      end

      def apply_layer_styles
        PSD.logger.debug "Applying layer styles to #{node.name}"
        LayerStyles.new(self).apply!
      end

      def apply_layer_opacity
        return if @node.root?
        PSD.logger.debug "Adjusting opacity for #{node.name}"
        
        @node.ancestors.each do |parent|
          break unless parent.passthru_blending?
          @opacity = (@opacity * parent.opacity.to_f) / 255.0
        end

        PSD.logger.debug "Inherited opacity for #{@node.debug_name} is #{@opacity}"
        @opacity = @opacity.to_i
      end

      def compose_pixels(base)
        Blender.new(self, base).compose!
      end
    end
  end
end
