require 'aws-sdk-translate'

module Tools
  # rubocop:disable Style/AsciiComments
  #
  # A translation utility library, backed by Aws Translate
  #
  # Usage:
  #
  #   translator = Tools::Translator.new
  #
  #   translator.translate(from: "en", to: "es-MX", text: "por que")
  #
  # Result:
  #
  #   {
  #     :from => {
  #       :language_code => :pt,
  #       :language_name => "Portuguese",
  #       :text => "por que",
  #       :auto_detect? => true,
  #       :new_language? => false,
  #     },
  #     :to => {
  #       :language_code => :es_MX,
  #       :language_name => "Spanish (Mexico)",
  #       :text => "por qué",
  #     },
  #   }
  #
  #
  # rubocop:enable Style/AsciiComments
  class TextTranslator

    # sourced from https://docs.aws.amazon.com/translate/latest/dg/what-is.html#what-is-languages
    SUPPORTED_LANGUAGES =
      { auto: "Detect Language" } # auto is always at the top
      .merge(
        {
          af: "Afrikaans",
          am: "Amharic",
          ar: "Arabic",
          az: "Azerbaijani",
          bg: "Bulgarian",
          bn: "Bengali",
          bs: "Bosnian",
          cs: "Czech",
          da: "Danish",
          de: "German",
          el: "Greek",
          en: "English",
          es: "Spanish",
          es_MX: "Spanish (Mexico)",
          es_mx: "Spanish (Mexico)",
          et: "Estonian",
          fa: "Persian",
          fa_AF: "Dari",
          fa_af: "Dari",
          fi: "Finnish",
          fr: "French",
          fr_CA: "French (Canada)",
          fr_ca: "French (Canada)",
          ha: "Hausa",
          he: "Hebrew",
          hi: "Hindi",
          hr: "Croatian",
          hu: "Hungarian",
          id: "Indonesian",
          it: "Italian",
          ja: "Japanese",
          ka: "Georgian",
          ko: "Korean",
          lv: "Latvian",
          ms: "Malay",
          nl: "Dutch",
          no: "Norwegian",
          pl: "Polish",
          ps: "Pashto",
          pt: "Portuguese",
          ro: "Romanian",
          ru: "Russian",
          sk: "Slovak",
          sl: "Slovenian",
          so: "Somali",
          sq: "Albanian",
          sr: "Serbian",
          sv: "Swedish",
          sw: "Swahili",
          ta: "Tamil",
          th: "Thai",
          tl: "Tagalog",
          tr: "Turkish",
          uk: "Ukrainian",
          ur: "Urdu",
          vi: "Vietnamese",
          zh: "Chinese (Simplified)",
          zh_TW: "Chinese (Traditional)",
          zh_tw: "Chinese (Traditional)",
        }
        .sort_by { |_, value| value } # sort the languages by Name
        .to_h # convert multi-dimensional array back into hash
      )

    def initialize(client: Aws::Translate::Client, options: {})
      if options[:region].nil?
        options[:region] =
          if defined?(Settings)
            Settings.aws_translate.region
          else
            "us-west-2"
          end
      end

      @translate_client ||= client.new(options)
    end

    #
    # Translate text from one language to another
    #
    # @param [Symbol] from source language
    # @param [Symbol] to target language
    # @param [String] text string to translate
    #
    # @raise [Aws::Translate::Errors::ServiceError] if AWS Translate returns an error.
    # @see Aws::Translate::Errors
    #   {https://github.com/aws/aws-sdk-ruby/blob/3dbe9/gems/aws-sdk-translate/lib/aws-sdk-translate/errors.rb documentation here}
    # @raise [RuntimeError] if we are unsuccessful, but unsure why
    #
    # @return [Hash{Symbol => Hash{Symbol => Symbol, String, Boolean}}, Hash{Symbol => String}]
    #   a hash containing language_code, language_name, whether source was done
    #   via auto_detect, and whether it's a new_language
    #
    def translate(from: "auto", to:, text:)
      # prepare inputs for the SDK
      from = massage_code_input(code: from)
      to = massage_code_input(code: to)

      # validate inputs and return any errors
      ok, err = validate_inputs(from: from, to: to)
      return {errors: err} if ok.nil?

      # call out to the AWS SDK api to actually translate
      resp = @translate_client.translate_text({
        text: text,
        source_language_code: from,
        target_language_code: to,
      })
      success = resp.successful?

      # validate ouputs and return any errors
      ok, err = validate_outputs(response: resp)
      return {errors: err} if ok.nil?

      # if we're still not successful at this point,
      # something unknown has gone /terribly/ wrong
      raise unless success

      # format the response language codes to match our internal list,
      # and denote whether this was an automatic detection, as well
      # as whether the returned language code is new to us
      source = massage_code_output(code: resp.source_language_code)
      target = massage_code_output(code: resp.target_language_code)
      auto = from == "auto"
      new_lang = SUPPORTED_LANGUAGES.key?(source) == false

      {
        from: {
          language_code: source,
          language_name: SUPPORTED_LANGUAGES[source],
          text: text,
          auto_detect?: auto,
          new_language?: new_lang,
        },
        to: {
          language_code: target,
          language_name: SUPPORTED_LANGUAGES[target],
          text: resp.translated_text,
        },
      }
    end

    #
    # validate input language codes
    #
    # @param [String, Symbol] from input language code
    # @param [String, Symbol] to input language code
    #
    # @return [Array(Symbol, Array<String>), Array(nil, Array)] A tuple of the status and discovered validation errors
    #
    def validate_inputs(from:, to:)
      from = massage_code_output(code: from)
      to = massage_code_output(code: to)

      err = []
      if to == "auto"
        err << "'automatic language detection' is not supported as a destination language"
      end

      if SUPPORTED_LANGUAGES.key?(to) == false
        err << "'#{to}' is not supported as a destination language"
      end

      if SUPPORTED_LANGUAGES.key?(from) == false
        err << "'#{from}' is not supported as a source language"
      end

      return [err.empty? ? :ok : nil, err]
    end

    #
    # validate response output
    #
    # @param [Aws::Translate::Types::TranslateTextResponse] response input language code
    #
    # @return [Array(Symbol, Aws::Translate::Errors::ServiceError), Array(nil, Array)] A tuple of the status and discovered validation errors
    #
    def validate_outputs(response:)
      err = []
      # the response object contains an error method that returns
      # captured errors from the SDK if something went wrong
      err = response.error unless response.error.nil?

      return [err.empty? ? :ok : nil, err]
    end

    #
    # Format languages codes for the AWS API
    #
    # @param [Symbol] code a language code in symbol form
    #
    # @return [String] a language code String ready for the Translate SDK
    #
    def massage_code_input(code:)
      code.to_s.sub('_', '-')
    end

    #
    # Format languages codes from the AWS API
    #
    # @param [String] code a language code String directly from the Translate SDK
    #
    # @return [Symbol] a language code in symbol form
    #
    def massage_code_output(code:)
      code.sub('-', '_').to_sym
    end

  end
end
