# encoding: utf-8

# reference.rb : Implementation of PDF indirect objects
#
# Copyright April 2008, Gregory Brown.  All Rights Reserved.
#
# This is free software. Please see the LICENSE and COPYING files for details.

require 'zlib'

module Prawn  
  module Core
  
    class Reference #:nodoc:

      attr_accessor :gen, :data, :offset, :stream, :live, :identifier
      
      def initialize(id, data)
        @identifier = id 
        @gen        = 0       
        @data       = data     
        @compressed = false
        @stream     = nil
      end            
      
      def object 
        output = "#{@identifier} #{gen} obj\n" <<
                 Prawn::Core::PdfObject(data) << "\n"
        if @stream
          output << "stream\n" << @stream << "\nendstream\n" 
        end
        output << "endobj\n"
      end  
      
      def <<(data)
        raise 'Cannot add data to a stream that is compressed' if @compressed
        (@stream ||= "") << data
        @data[:Length] = @stream.length
        @stream
      end  
      
      def to_s            
        "#{@identifier} #{gen} R"
      end

      def compress_stream
        @stream = Zlib::Deflate.deflate(@stream)
        @data[:Filter] = :FlateDecode
        @data[:Length] = @stream.length
        @compressed = true
      end

      def compressed?
        @compressed
      end

      # Creates a deep copy of this ref. If +share+ is provided, shares the
      # given dictionary entries between the old ref and the new.
      #
      def deep_copy(share=[])
        r = dup

        case r.data
        when Hash
          # Copy each entry not in +share+.
          (r.data.keys - share).each do |k|
            r.data[k] = Marshal.load(Marshal.dump(r.data[k]))
          end
        when Prawn::Core::NameTree::Node
          r.data = r.data.deep_copy
        else
          r.data = Marshal.load(Marshal.dump(r.data))
        end

        r.stream = Marshal.load(Marshal.dump(r.stream))
        r
      end
      
      # Replaces the data and stream with that of other_ref. Preserves compressed
      # status.
      def replace(other_ref)
        @data       = other_ref.data
        @stream     = other_ref.stream
        @compressed = other_ref.compressed?
      end

      # Marks this and all referenced objects live, recursively.
      def mark_live
        return if @live
        @live = true
        referenced_objects.each { |o| o.mark_live }
      end

      private

      # All objects referenced by this one. Used for GC.
      def referenced_objects(obj=@data)
        case obj
        when self.class
          []
        when Hash
          obj.values.map{|v| [v] + referenced_objects(v) }
        when Array
          obj.map{|v| [v] + referenced_objects(v) }
        when OutlineRoot, OutlineItem
          referenced_objects(obj.to_hash)
        else []
        end.flatten.grep(self.class)
      end

    end         

    module_function
    
    def Reference(*args, &block) #:nodoc:
      Reference.new(*args, &block)
    end     
  end
end
