require 'generators/i18n_translation/lib/yaml'
require 'generators/i18n_translation/lib/translator'

class I18nTranslationGenerator < Rails::Generators::NamedBase
  # option: include_timestamps
  def main
    unless file_name =~ /^[a-zA-Z]{2}([-_][a-zA-Z]+)?$/
      log 'ERROR: Wrong locale format. Please input in ?? or ??-?? format.'
      exit
    end
    log "translating models to #{locale_name}..."
    I18n.locale = locale_name
    Rails.application.eager_load!

    # activerecord:models comes first
    model_names_translations = order_hash translate_all(model_names_keys)
    # activerecord:models:attributes comes next
    attribute_names_translations = ActiveSupport::OrderedHash.new
    attribute_names_translations.merge! translate_all(content_column_names_keys)
    attribute_names_translations.merge! translate_all(collection_reflection_names_keys)
    # refer to already translated model names
    attribute_names_translations.merge! singular_reflection_names_references
    # merge them all
    translations = model_names_translations.deep_merge order_hash(attribute_names_translations)

    yaml = I27r::YamlDocument.load_yml_file "config/locales/translation_#{locale_name}.yml"
    each_value [], translations do |parents, value|
      if value.is_a?(String) || value.is_a?(Symbol)
        yaml[[locale_name.to_s] + parents] = value
      else
        value.each do |key, val|
          yaml[[locale_name.to_s] + parents + [key.to_s]] = val
        end
      end
    end

    unless (yaml_string = yaml.to_s(true)).blank?
      create_file "config/locales/translation_#{locale_name}.yml", yaml_string
    end
  end

  private
  def models
    ar_descendants = ActiveRecord::Base.descendants
    ar_descendants.delete(ActiveRecord::SchemaMigration) if defined?(ActiveRecord::SchemaMigration)
    @models ||= ar_descendants.map do |m|
      begin
        m if m.table_exists? && m.respond_to?(:content_columns)
      rescue => e
        p e
        next
      end
    end.compact
  end

  def model_names_keys
    models.map {|m| "activerecord.models.#{m.model_name.to_s.underscore}"}
  end

  def content_column_names_keys
    models.map {|m|
      cols = m.content_columns.map {|c| c.name}
      cols.delete_if {|c| %w[created_at updated_at].include? c} unless include_timestamps?
      cols.map {|c| "activerecord.attributes.#{m.model_name.to_s.underscore}.#{c}"}
    }.flatten
  end

  def singular_reflection_names_references
    ret = {}
    models.each do |m|
      m.reflect_on_all_associations.select {|c| !c.collection?}.each do |c|
        ret["activerecord.attributes.#{m.model_name.to_s.underscore}.#{c.name}"] = "activerecord.models.#{c.name}".to_sym
      end
    end
    ret
  end

  def collection_reflection_names_keys
    models.map {|m|
      m.reflect_on_all_associations.select {|c| c.collection?}.map {|c| "activerecord.attributes.#{m.model_name.to_s.underscore}.#{c.name}"}
    }.flatten
  end

  def translator
    @translator ||= I27r::Translator.new locale_name.sub(/\-.*/, '')
  end

  # receives an array of keys and returns :key => :translation hash
  def translate_all(keys)
    ret, threads = {}, []
    keys.each do |key|
      threads << Thread.new do
        Rails.logger.debug "translating #{key} ..."
        Thread.pass
        key_prefix, key_suffix = key.to_s.split('.')[0...-1], key.to_s.split('.')[-1]
        existing_translation = I18n.backend.send(:lookup, locale_name, key_suffix, key_prefix)
        ret[key] = existing_translation ? existing_translation : translator.translate(key_suffix)
      end
    end
    threads.each {|t| t.join}
    ret
  end

  def locale_name
    @_locale_name ||= file_name.gsub('_', '-').split('-').each.with_index.map {|s, i| i == 0 ? s : s.upcase}.join('-')
  end

  def include_timestamps?
    !!@include_timestamps
  end

  # transform a Hash into a nested OrderedHash
  def order_hash(hash)
    ActiveSupport::OrderedHash.new.tap do |oh|
      hash.sort_by {|k, _v| k}.each do |key, value|
        if key.to_s.include? '.'
          key_prefix, key_suffix = key.to_s.split('.')[0...-1], key.to_s.split('.')[-1]
          key_prefix.inject(oh) {|h, k| h[k] ||= ActiveSupport::OrderedHash.new}[key_suffix] = value
        else
          oh[key] = value
        end
      end
    end
  end

  # iterate through all values
  def each_value(parents, src, &block)
    src.each do |k, v|
      if v.is_a?(ActiveSupport::OrderedHash)
        each_value parents + [k], v, &block
      else
        yield parents + [k], v
      end
    end
  end
end