=begin
  rmsgmerge.rb - Merge old .po to new .po

  Copyright (C) 2005,2006 Masao Mutoh
  Copyright (C) 2005,2006 speakillof

  You may redistribute it and/or modify it under the same
  license terms as Ruby.
=end

require 'optparse'
require 'gettext'
require 'gettext/poparser'
require 'rbconfig'

module GetText
    
  module RMsgMerge
    
    class PoData  #:nodoc:  
      
      attr_reader :msgids
      
      def initialize
        @msgid2msgstr = {}
        @msgid2comment = {}
        @msgids = []
      end
      
      def set_comment(msgid_or_sym, comment)
        @msgid2comment[msgid_or_sym] = comment      
      end
      
      def msgstr(msgid)
        @msgid2msgstr[msgid]
      end
      
      def comment(msgid)
        @msgid2comment[msgid]
      end
      
      def [](msgid)
        @msgid2msgstr[msgid]
      end
      
      def []=(msgid, msgstr)
        # Retain the order
        unless @msgid2msgstr[msgid]
          @msgids << msgid
        end
        
        @msgid2msgstr[msgid] = msgstr
      end
      
      def each_msgid
        arr = @msgids.delete_if{|i| Symbol === i or i == ''}
        arr.each do |i|
          yield i
        end
      end
      
      def msgid?(msgid)
        !(Symbol === msgid) and  @msgid2msgstr[msgid] and (msgid != '')
      end
      
      # Is it necessary to implement this method?
      def search_msgid_fuzzy(msgid, used_msgids)
        nil
      end
      
      def nplural
        unless @msgid2msgstr['']
          return 0
        else
          if /\s*nplural\s*=\s*(\d+)/ =~ @msgid2msgstr['']
            return $1.to_i
          else
            return 0
          end
          
        end
      end
      
      def generate_po
        str = ''
        str << generate_po_header
        
        self.each_msgid do |id|
          str << self.generate_po_entry(id)          
        end
        
        str << @msgid2comment[:last]        
        str        
      end
      
      def generate_po_header
        str = ""
        
        str << @msgid2comment[''].strip << "\n"
        str << 'msgid ""'  << "\n"                
        str << 'msgstr ""' << "\n"
        msgstr = @msgid2msgstr[''].gsub(/"/, '\"').gsub(/\r/, '')
        msgstr = msgstr.gsub(/^(.*)$/, '"\1\n"')
        str << msgstr
        str << "\n"
        
        str
      end
      
      def generate_po_entry(msgid)
        str = ""
        str << @msgid2comment[msgid]
        if str[-1] != "\n"[0]
          str << "\n"
        end
        
        id = msgid.gsub(/"/, '\"').gsub(/\r/, '')
        msgstr = @msgid2msgstr[msgid].gsub(/"/, '\"').gsub(/\r/, '')

        if id.include?("\000")
          ids = id.split(/\000/)          
          str << "msgid " << __conv(ids[0]) << "\n"
          ids[1..-1].each do |single_id|
            str << "msgid_plural " << __conv(single_id) << "\n"
          end
          
          msgstr.split("\000").each_with_index do |m, n|
            str << "msgstr[#{n}] " << __conv(m) << "\n"
          end
        else
          str << "msgid "  << __conv(id) << "\n"
          str << "msgstr " << __conv(msgstr) << "\n"
        end
        
        str << "\n"
        str
      end
      
      def __conv(str)
        s = ''

        if str.count("\n") > 1
          s << '""' << "\n"
          s << str.gsub(/^(.*)$/, '"\1\n"')
        else
          s << '"' << str.sub("\n", "\\n") << '"'
        end
        
        s.rstrip
      end
      
    end
    
    class Merger #:nodoc:
      
      # From GNU gettext source.
      # 
      # Merge the reference with the definition: take the #. and
      #	#: comments from the reference, take the # comments from
	  # the definition, take the msgstr from the definition.  Add
	  # this merged entry to the output message list.      
      DOT_COMMENT_RE = /\A#\./
      SEMICOLON_COMMENT_RE = /\A#\:/
      FUZZY_RE = /\A#\,/
      NOT_SPECIAL_COMMENT_RE = /\A#([^:.,]|\z)/
      
      CRLF_RE = /\r?\n/
      POT_DATE_EXTRACT_RE = /POT-Creation-Date:\s*(.*)?\s*$/
      POT_DATE_RE = /POT-Creation-Date:.*?$/
      
      def merge(definition, reference)
        # deep copy
        result = Marshal.load( Marshal.dump(reference) )        
        
        used = []        
        merge_header(result, definition)

        result.each_msgid do |msgid|
          if definition.msgid?(msgid)
            used << msgid
            merge_message(msgid, result, msgid, definition)
          elsif other_msgid = definition.search_msgid_fuzzy(msgid, used)
            used << other_msgid
            merge_fuzzy_message(msgid, result, other_msgid, definition) 
          elsif msgid.index("\000") and ( reference.msgstr(msgid).gsub("\000", '') == '' )
            # plural
            result[msgid] = "\000" * definition.nplural
          else
            change_reference_comment(msgid, result)
          end          
        end
        
        ###################################################################
        # msgids which are not used in reference are handled as obsolete. #
        ################################################################### 
        last_comment = result.comment(:last) || ''
        definition.each_msgid do |msgid|
          unless used.include?(msgid)
            last_comment << "\n"
            last_comment << definition.generate_po_entry(msgid).strip.gsub(/^/, '#. ')
            last_comment << "\n"
          end
        end
        result.set_comment(:last, last_comment)
        
        result
      end
      
      def merge_message(msgid, target, def_msgid, definition)
        merge_comment(msgid, target, def_msgid, definition)
        
        ############################################
        # check mismatch of msgid and msgid_plural #
        ############################################
        def_msgstr = definition[def_msgid]
        if msgid.index("\000")
          if def_msgstr.index("\000")
            # OK
            target[msgid] = def_msgstr			
          else
            # NG
            s = ''            
            definition.nplural.times {
              s << def_msgstr
              s << "\000"
            }
            target[msgid] = s
          end
        else
          if def_msgstr.index("\000")
            # NG
            target[msgid] = def_msgstr.split("\000")[0]
          else
            # OK
            target[msgid] = def_msgstr
          end
        end
      end
      
      # for the future
      def merge_fuzzy_message(msgid, target, def_msgid, definition)
        merge_message(msgid, target, def_msgid, definition)
      end
      
      def merge_comment(msgid, target, def_msgid, definition)
        ref_comment = target.comment(msgid)
        def_comment = definition.comment(def_msgid)
        
        normal_comment = []
        dot_comment = []
        semi_comment = []
        is_fuzzy = false
        
        def_comment.split(CRLF_RE).each do |l| 
          if NOT_SPECIAL_COMMENT_RE =~ l
            normal_comment << l
          end           
        end
        
        ref_comment.split(CRLF_RE).each do |l|
          if DOT_COMMENT_RE =~ l 
            dot_comment << l
          elsif SEMICOLON_COMMENT_RE =~ l
            semi_comment << l
          elsif FUZZY_RE =~ l
            is_fuzzy = true
          end
        end
        
        str = format_comment(normal_comment, dot_comment, semi_comment, is_fuzzy)
        target.set_comment(msgid, str)
      end
      
      def change_reference_comment(msgid, podata)
        normal_comment = []
        dot_comment = []
        semi_comment = []
        is_fuzzy = false
        
        podata.comment(msgid).split(CRLF_RE).each do |l|
          if DOT_COMMENT_RE =~ l 
            dot_comment << l
          elsif SEMICOLON_COMMENT_RE =~ l
            semi_comment << l
          elsif FUZZY_RE =~ l
            is_fuzzy = true
          else
            normal_comment << l
          end
        end
        
        str = format_comment(normal_comment, dot_comment, semi_comment, is_fuzzy)
        podata.set_comment(msgid, str)        
      end
      
      def format_comment(normal_comment, dot_comment, semi_comment, is_fuzzy)
        str = ''
        
        str << normal_comment.join("\n").gsub(/^#(\s*)/){|sss|
          if $1 == ""
            "# "
          else
            sss
          end
        }
        if normal_comment.size > 0
          str << "\n"
        end
        
        str << dot_comment.join("\n").gsub(/^#.(\s*)/){|sss|
          if $1 == ""
            "#. "
          else
            sss
          end
        }
        if dot_comment.size > 0
          str << "\n"
        end
        
        str << semi_comment.join("\n").gsub(/^#:\s*/, "#: ")
        if semi_comment.size > 0
          str << "\n"
        end
        
        if is_fuzzy
          str << "#, fuzzy\n"
        end
        
        str
      end
      
      def merge_header(target, definition)
        merge_comment('', target, '', definition)
        
        msg = target.msgstr('')
        def_msg = definition.msgstr('')
        if POT_DATE_EXTRACT_RE =~ msg
          time = $1
          def_msg = def_msg.sub(POT_DATE_RE, "POT-Creation-Date: #{time}")
        end
        
        target[''] = def_msg
      end
      
    end
    
  end  
  
end

module GetText::RMsgMerge #:nodoc:

  class Config #:nodoc:
    
    attr_accessor :defpo, :refpot, :output, :fuzzy, :update
    
    # update mode options
    attr_accessor :backup, :suffix
    
=begin      
The result is written back to def.po.
      --backup=CONTROL        make a backup of def.po
      --suffix=SUFFIX         override the usual backup suffix
The version control method may be selected via the --backup option or through
the VERSION_CONTROL environment variable.  Here are the values:
  none, off       never make backups (even if --backup is given)
  numbered, t     make numbered backups
  existing, nil   numbered if numbered backups exist, simple otherwise
  simple, never   always make simple backups
The backup suffix is `~', unless set with --suffix or the SIMPLE_BACKUP_SUFFIX
environment variable.
=end      
      
    def initialize
      @output = STDOUT
      @fuzzy = nil
      @update = nil
      @backup = ENV["VERSION_CONTROL"]
      @suffix= ENV["SIMPLE_BACKUP_SUFFIX"] || "~"
      @input_dirs = ["."]
    end
    
  end
  
end

module GetText
  
  module RMsgMerge
    extend GetText

    bindtextdomain("rgettext")
    
    # constant values
    VERSION = GetText::VERSION
    DATE = %w($Date: 2006/06/11 15:36:20 $)[1]
    
    module_function

    def check_options(config)
      opts = OptionParser.new
      opts.banner = _("Usage: %s def.po ref.pot [-o output.pot]") % $0
      #opts.summary_width = 80
      opts.separator("")
      opts.separator(_("Merges two Uniforum style .po files together. The def.po file is an existing PO file with translations. The ref.pot file is the last created PO file with up-to-date source references. ref.pot is generally created by rgettext."))
      opts.separator("")
      opts.separator(_("Specific options:"))
      
      opts.on("-o", "--output=FILE", _("write output to specified file")) do |out|
        unless FileTest.exist? out
          config.output = out
        else
          #$stderr.puts(_("File '%s' has already existed.") % out)
          #exit 1
        end
      end
      
      #opts.on("-F", "--fuzzy-matching")
      
      opts.on_tail("--version", _("display version information and exit")) do
        puts "#{$0} #{VERSION} (#{DATE})"
	puts "#{File.join(::Config::CONFIG["bindir"], ::Config::CONFIG["RUBY_INSTALL_NAME"])} #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
        exit
      end
      
      opts.parse!(ARGV)
      
      if ARGV.size != 2
        puts opts.help
        exit 1
      end
      
      config.defpo = ARGV[0]
      config.refpot = ARGV[1]
    end
    
    def run(reference = nil, definition = nil, out = STDOUT)
      config = GetText::RMsgMerge::Config.new
      config.refpot = reference
      config.defpo = definition
      config.output = out
      
      check_options(config)
      
      if config.defpo.nil?
        raise ArgumentError, _("definition po is not given.")
      elsif config.refpot.nil? 
        raise ArgumentError, _("reference pot is not given.")
      end
      
      parser = PoParser.new
      defstr = nil
      refstr = nil      
      File.open(config.defpo){|f| defstr = f.read}
      File.open(config.refpot){|f| refstr = f.read}
      defpo = parser.parse(defstr, PoData.new, false)
      refpot = parser.parse(refstr, PoData.new, false)
      
      m = Merger.new
      result = m.merge(defpo, refpot)      
      pp result if $DEBUG
      print result.generate_po if $DEBUG
      
      begin
        if out.is_a? String
          File.open(File.expand_path(out), "w+") do |file|
            file.write(result.generate_po)
          end
        else
          out.puts(result.generate_po)
        end
      ensure
        out.close
      end
    end    
    
  end
  
end



module GetText
  
  module_function

  # Experimental
  def rmsgmerge(reference = nil, definition = nil, out = STDOUT)
    RMsgMerge.run(reference, definition, out)
  end

end



if $0 == __FILE__ then
  require 'pp'
  
  #parser = GetText::RMsgMerge::PoParser.new;
  #parser = GetText::PoParser.new;
  #pp parser.parse(ARGF.read)
  
  GetText.rmsgmerge
end
