# coding: utf-8
require "swiftlocalizer/version"
require "optparse"

module Swiftlocalizer
  class LocalizedString
    def initialize(en, ja, filename, lineno)
      @en = en
      @ja = ja
      @filename = filename
      @lineno = lineno
    end
    attr_accessor :en, :ja
    attr_accessor :filename, :lineno
    def to_s
      "#{@en},#{@ja},#{@filename},#{@lineno}"
    end

    def str_and_lineno
      "#{@filename}: #{@en} #{@ja}"
    end

    def to_short_s
      "#{@en} #{@ja} #{File.basename(@filename)}:#{@lineno}"
    end

    def key
      @en
    end
  end

  class LocalizableString
    def initialize(str, filename, lineno)
      @str = str
      @filename = filename
      @lineno = lineno
    end
    attr_accessor :str, :filename, :lineno
    def to_s
      "#{@str},#{@filename},#{@lineno}"
    end
    def str_and_lineno
      "#{@lineno}: #{@str}"
    end
    def to_short_s
      "#{@str} #{File.basename(@filename)}:#{@lineno}"
    end        
  end

  class Command

    NSLOCALIZED_STRING_REGEX = /NSLocalizedString\("([^"]+)",\s*comment:\s*"([^"]+)"\)/
    PRINT_REGEX = /print\("([^"]+)"\)/
    COMMENT_REGEX = /\/\/.*$/
    
    def self.get_localized_strings_from_file(file)
      get_localized_strings_from_lines(File.readlines(file), file)
    end

    def self.get_localized_strings_from_lines(lines, file)
      strings = []
      lines.each_with_index do |line, index|
        strings.concat(get_localized_strings_from_line(line, file, index + 1))
      end
      strings
    end
    
    def self.get_localized_strings_from_line(line, file, lineno)
      strings = []
      while line =~ NSLOCALIZED_STRING_REGEX
        string = LocalizedString.new($1, $2, file, lineno)
        strings << string
        line = $'
      end
      strings
    end

    def self.get_localizable_strings_from_file(file)
      get_localizable_strings_from_lines(File.readlines(file), file)
    end

    def self.get_localizable_strings_from_lines(lines, file)
      strings = []
      lines.each_with_index do |line, index|
        strings.concat(get_localizable_strings_from_line(line, file, index + 1))
      end
      strings
    end

    def self.get_localizable_strings_from_line(line, file, lineno)
      strings = []
      matched = true
      while matched 
        if line =~ NSLOCALIZED_STRING_REGEX || line =~ PRINT_REGEX || line =~ COMMENT_REGEX
          line = $'
          next
        end
        if line =~ /"([^"]+)"/
          if jstring?($1)
            string = LocalizableString.new($1, file, lineno)
            strings << string
          end
          line = $'
        else
          matched = false
        end
      end
      strings      
    end

    def self.jstring?(str)
      str.bytesize > str.size
    end
    
    def self.run(argv)
      STDOUT.sync = true
      opts = {}
      opt = OptionParser.new(argv)
      opt.banner = "Usage: #{opt.program_name} [-h|--help] [config.yml]"
      opt.version = Swiftlocalizer::VERSION
      opt.separator('')
      opt.separator "Options:"
      opt.on_head('-h', '--help', 'Show this message') do |v|
        puts opt.help
        exit
      end
      opt.on('-v', '--verbose', 'Verbose message') {|v| opts[:v] = v}
      opt.on('-n', '--dry-run', 'Message only') {|v| opts[:n] = v}
      opt.on('-c', '--check', 'Check localizable strings') {|v| opts[:c] = v}
      opt.parse!(argv)
      dir = ARGV.shift || '.'
      command = Command.new(opts, dir)
      command.run
    end

    def initialize(opts, dir)
      @opts = opts
      @dir = dir
    end

    def run
      if @opts[:c]
        check_strings
      else
        scan_and_write
      end
    end

    private
    def check_strings
      strings = scan_sources do |f|
        Command.get_localizable_strings_from_file(f)
      end
    end
    
    def scan_and_write
      strings = scan_sources do |f|
        Command.get_localized_strings_from_file(f)
      end

      check_duplicate(strings)
      
      basename = 'Localizable.strings'
      
      path = File.join(@dir, 'Base.lproj', basename)
      write_localizable_strings(strings, path, :en)
      
      path = File.join(@dir, 'ja.lproj', basename)      
      write_localizable_strings(strings, path, :ja)      
    end
    
    def scan_sources
      puts "Scan #{@dir}"  
      strings = []
      Dir.glob(@dir + '/**/*.swift').each do |f|
        puts f
        file_strings = yield(f)
        puts "retrieve #{file_strings.size} strings"
        file_strings.each{|str| puts "\t#{str.str_and_lineno}\n"}
        strings.concat(file_strings)
      end
      strings
    end

    def check_duplicate(strings)
      puts "Check duplicate"
      hash_en = {}
      strings.each do |str|
        exists = hash_en[str.en]
        if exists
          puts "\t" + exists.to_short_s + " <=> " + str.to_short_s + "\n"
        else
          hash_en[str.en] = str
        end
      end
    end

    def write_localizable_strings(strings, path, sym)
      puts "Write #{path}" 
      unless FileTest.file?(path)
        raise RuntimeError, "#{path} doesn't exist."
      end
      File.open(path, 'w') do |f|
        strings.sort_by{|a| a.key }.each do |str|
          en = str.send(:en)
          localized = str.send(sym.to_sym)
          f.puts "\"#{en}\" = \"#{localized}\";"
        end
      end
    end
  end
end