# -*- coding: utf-8 -*-
#
# ITextPrinter クラス
#   基本的な動作例は lib/itext.rb#to_pdf を参照
#
class ITextPrinter
  attr_accessor :writer
  attr_accessor :outfile

  def initialize( options={} )
    Rjb::load(classpath = File.expand_path(File.join(File.dirname(__FILE__), 'iText-5.0.2_J.jar')), jvmargs=['-Xms256m', '-Xmx512m'])
    @filestream   = Rjb::import('java.io.FileOutputStream')
    @acrofields   = Rjb::import('com.itextpdf.text.pdf.AcroFields')
    @pdfreader    = Rjb::import('com.itextpdf.text.pdf.PdfReader')
    @pdfstamper   = Rjb::import('com.itextpdf.text.pdf.PdfStamper')
    @pdfcopyfields = Rjb::import('com.itextpdf.text.pdf.PdfCopyFields')
    set_default_options(options)
  end

  def set_default_options(options={})
    @options = options
    @options[:searchpath] ||= File.join(RAILS_ROOT, "app", "views", "layouts")
    @options[:template] ||= "proposal_template.pdf"
    @options[:per_page] ||= 1
    @options[:basename] ||= 'items'
    @options[:title]   ||= 'Title'
    @options[:author]  ||= 'Author'
    @options[:creator] ||= 'Creator'
    # default block option は a=b 形式，スペース区切りで最後にスペースをつけないこと
    @options[:global] ||= {}
  end

  def set_parameters
    # 未実装
  end

  # ドキュメントの開始宣言
  # document_end と対で呼ばれる
  def document_start
    @writer = @pdfcopyfields.new( @filestream.new( get_outfile() ) )
    @writer.open()
  end

  # ドキュメントの終了宣言
  # document_start と対で呼ばれる
  def document_end
    @writer.close()
    for file in @tmpfile_stack
      File.delete(file)
    end
  end

  # [insert_page]
  #   data で指定されたハッシュに基づいてページを生成します
  #   data は基本的には Array.to_pdf_json メソッドで生成したものを使うことを想定しています
  #
  # [data がハッシュの場合]
  #   改ページはされない．
  #   data = {"hoge" => "fuga", "foo" => "bar"} なら
  #     items.hoge = fuga
  #     items.foo  = bar
  #   となります．basename と要素の間に数字は入りません
  # 
  # [data が配列の場合]
  #   表など，1ページに複数の要素を配置したい場合は data を配列にすると自動的に要素名を
  #   決定してくれます．per_page オプションで指定の要素数で改ページすることもできます． 
  #
  #   options[:per_page] で指定された要素区切りで改ページされます
  #   options[:per_page] = 2 なら data[0],data[1] が1ページ目， data[2],data[3] が2ページ目
  #
  #   data = [ {"hoge" => "fuga"}, {"foo" => "bar"} ] で per_page = 2 なら
  #     items.0.hoge = fuga (1ページ目)
  #     items.1.foo  = bar  (1ページ目)
  #   というように，basename と要素の間に数字が必要になります．per_page = 1 の時も
  #     items.0.hoge = fuga (1ページ目)
  #     items.0.foo  = bar  (2ページ目)
  #   と，0 が必要なので注意してください
  #
  # [ちょっと複雑な例]
  #   data = [ {"hoge" => [{"foo" => "bar"}, {"fizz", "buzz"}] } ] などとなっている時は
  #     items.0.hoge.0.foo  = bar 
  #     items.0.hoge.1.fizz = buzz
  #   となります．この時，改ページの対象となるのは1次の配列( data[0],data[1]... )
  #   のみです．
  def insert_page(data, insert_page_options={})
    if(data.instance_of?(Array) && @options[:per_page] > 0)
      0.step(data.length-1, @options[:per_page]) {|i_from|
        i_to = i_from + @options[:per_page] - 1
        i_to = i_to >= data.length ? -1 : i_to
        new_page(insert_page_options)
        assign(@options[:global], insert_page_options, @options[:basename]) # 全ページ共通項目
        assign(data[i_from..i_to], insert_page_options, @options[:basename]) # block_options を指定したければする
        end_page
      }
    elsif(data.instance_of?(Hash))
      new_page(insert_page_options)
      assign(@options[:global], insert_page_options, @options[:basename]) # 全ページ共通項目
      assign(data, insert_page_options, @options[:basename]) # block_options を指定したければする
      end_page
    end
  end

  # ヘッダやフッタなど特定のページを挿入するための関数
  # target 引数には以下の形式のハッシュを渡す
  # target = { 
  #            :data => { 出力データハッシュ．形式は insert_pageと同じ }, 
  #            :options => { insert_page_options ハッシュ．一般的には :template などを指定 }
  #          }
  # insert_page_options は tpl_page_num などを主に指定します
  def insert_specific_page(target)
    if(!target.nil?)
      data    = target[:data].nil? ? {} : target[:data]
      insert_page_options = target[:options].nil? ? {} : target[:options]
      insert_page(data, insert_page_options)
    end
  end

  # ページの開始処理
  # new_page を行った場合必ず end_page を呼ぶこと
  def new_page(insert_page_options = {})
    insert_page_options[:template] ||= @options[:template]
    reader = @pdfreader.new( File.expand_path(File.join(@options[:searchpath], insert_page_options[:template]) ) )
    @stamp = @pdfstamper.new(reader, @filestream.new( tmpfile() ) )
    @form = @stamp.getAcroFields()
  end

  # ページの終了処理
  # new_page と対で呼ばれる
  def end_page
    @stamp.setFormFlattening(true)
    @stamp.close
    reader = @pdfreader.new( @tmpfile )
    @writer.addDocument(reader)
  end

  # 変数のアサイン
  def assign( data, block_options={}, basename='items' )
    if(data.instance_of?(Hash))
      data.each { |key,value|
        name = basename + '.' + key.to_s
        assign(value, block_options, name)
      }
    elsif(data.instance_of?(Array))
      for i in 0..data.length-1 do
        name = basename + '.' + i.to_s
        assign(data[i], block_options, name)
      end
    else
      @form.setField( basename, data.to_s )
      #puts "assign #{basename}: #{data.to_s}"
    end
  end

  # 変数のアサイン
  def print_assign_names( data, block_options={}, basename='items' )
    if(data.instance_of?(Hash))
      data.each { |key,value|
        name = basename + '.' + key.to_s
        print_assign_names(value, block_options, name)
      }
    elsif(data.instance_of?(Array))
      for i in 0..data.length-1 do
        name = basename + '.' + i.to_s
        print_assign_names(data[i], block_options, name)
      end
    else
      puts "#{basename}=#{data.to_s}"
    end
  end
  
  # PDFStamperクラス用テンポラリファイル名をランダムに作成して変数にセットします
  def tmpfile
    @tmpfile_stack = Array.new if @tmpfile_stack.nil?
    @tmpfile_stack << File.join( Dir::tmpdir, make_tmpname )
    @tmpfile = @tmpfile_stack[-1]
  end

  # PdfCopyFieldsクラス用テンポラリファイル名をランダムに作成して変数にセットします
  def get_outfile
    return @outfile unless @outfile.nil?
    @outfile = File.join( Dir::tmpdir, make_tmpname )
  end

  # PDFバッファを返して、テンポラリファイルを全て削除します
  def display
    buffer = File.open( @outfile ).read
    File.delete(@outfile)
    for file in @tmpfile_stack
      File.delete(file)
    end
    buffer
  end

  private

  # テンポラリファイル名をランダムに生成します
  def make_tmpname
    return 'itextprinter-' + rand(100000).to_s + '.pdf'
  end
end
