# encoding: UTF-8

# Copyright 2012 Twitter, Inc
# http://www.apache.org/licenses/LICENSE-2.0

require 'mustache'
require 'uglifier'
require 'coffee-script'

module TwitterCldr
  module Js
    CompiledFile = Struct.new(:source, :source_map)

    class Compiler
      attr_reader :locales

      def initialize(options = {})
        @locales = options[:locales] || TwitterCldr.supported_locales
        @features = options[:features] || implementation_renderers.keys
        @data = options[:features] || data_renderers.keys
        @test_helpers = options[:test_helpers] || test_helper_renderers.keys
        @prerender = options[:prerender].nil? ? true : options[:prerender]
        @source_map = options[:source_map]
      end

      def compile_bundle(bundle, bundle_elements, bundle_hash, options = {})
        contents = ""
        bundle_elements.each do |bundle_element|
          if renderer_const = bundle_hash[bundle_element]
            if bundle[:locale]
              contents << renderer_const.new(:locale => bundle[:locale], :prerender => @prerender).render
            else
              contents << renderer_const.new(:prerender => @prerender).render
            end
          end
        end

        bundle[:contents] = contents
        bundle[:source_map] = @source_map

        result = CoffeeScript.compile(bundle.render, {
          :bare => false,
          :sourceMap => @source_map
        })

        file = if @source_map
          CompiledFile.new(result["js"], result["sourceMap"])
        else
          CompiledFile.new(result)
        end

        file.source = post_process_file(file.source, options)
        file
      end

      def post_process_file(file_source, options)
        options[:minify] = true unless options.include?(:minify)
        # required alias definition that adds twitter_cldr to Twitter's static build process
        file_source.gsub!(/\/\*<<module_def>>\s+\*\//, %Q(/*-module-*/\n/*_lib/twitter_cldr_*/))

        if options[:minify]
          file_source = Uglifier.compile(file_source)
        end

        file_source
      end

      def compile_each(options = {})
        @locales.each do |locale|
          bundle = TwitterCldr::Js::Renderers::DataBundle.new
          bundle[:locale] = locale

          bundle[:contents] = data_renderers.inject({}) do |ret, (data_renderer_name, data_renderer_class)|
            data_renderer = data_renderer_class.new(:locale => locale)
            data = data_renderer.get_data
            data.each_pair do |name, value|
              ret[name] = value
            end

            ret
          end.to_json

          file = post_process_file(CoffeeScript.compile(bundle.render), options)

          yield file, TwitterCldr.twitter_locale(locale)
        end
      end

      def compile_implementation(options = {})
        bundle = TwitterCldr::Js::Renderers::Bundle.new
        bundle[:locale] = TwitterCldr::DEFAULT_LOCALE

        available_locales = @locales.map do |locale|
          {
            locale: locale,
            file_name: TwitterCldr.twitter_locale(locale),
          }
        end

        bundle[:available_locales] = available_locales.sort_by { |x| x[:locale] }.to_json

        file = compile_bundle(bundle, @features, implementation_renderers, options)

        file.source
      end

      def compile_test(options = {})
        bundle = TwitterCldr::Js::Renderers::TestBundle.new
        file = compile_bundle(bundle, @test_helpers, test_helper_renderers, options)
        file.source
      end

      private

      def implementation_renderers
        @implementation_renderers ||= {
          :settings                        => TwitterCldr::Js::Renderers::Implementation::Settings::SettingsRenderer,
          :plural_rules                    => TwitterCldr::Js::Renderers::Implementation::PluralRules::PluralRulesRenderer,
          :timespan                        => TwitterCldr::Js::Renderers::Implementation::Calendars::TimespanRenderer,
          :datetime                        => TwitterCldr::Js::Renderers::Implementation::Calendars::DateTimeRenderer,
          :additional_date_format_selector => TwitterCldr::Js::Renderers::Implementation::Calendars::AdditionalDateFormatSelectorRenderer,
          :currencies                      => TwitterCldr::Js::Renderers::Implementation::Shared::CurrenciesRenderer,
          :lists                           => TwitterCldr::Js::Renderers::Implementation::Shared::ListRenderer,
          :bidi                            => TwitterCldr::Js::Renderers::Implementation::Shared::BidiRenderer,
          :break_iterator                  => TwitterCldr::Js::Renderers::Implementation::Shared::BreakIteratorRenderer,
          :calendar                        => TwitterCldr::Js::Renderers::Implementation::Shared::CalendarRenderer,
          :code_point                      => TwitterCldr::Js::Renderers::Implementation::Shared::CodePointRenderer,
          :numbering_systems               => TwitterCldr::Js::Renderers::Implementation::Shared::NumberingSystemsRenderer,
          :phone_codes                     => TwitterCldr::Js::Renderers::Implementation::Shared::PhoneCodesRenderer,
          :postal_codes                    => TwitterCldr::Js::Renderers::Implementation::Shared::PostalCodesRenderer,
          :languages                       => TwitterCldr::Js::Renderers::Implementation::Shared::LanguagesRenderer,
          :unicode_regex                   => TwitterCldr::Js::Renderers::Implementation::Shared::UnicodeRegexRenderer,
          :territories_containment         => TwitterCldr::Js::Renderers::Implementation::Shared::TerritoriesContainmentRenderer,
          :number_parser                   => TwitterCldr::Js::Renderers::Implementation::Parsers::NumberParser,
          :component                       => TwitterCldr::Js::Renderers::Implementation::Parsers::UnicodeRegex::ComponentRenderer,
          :literal                         => TwitterCldr::Js::Renderers::Implementation::Parsers::UnicodeRegex::LiteralRenderer,
          :unicode_string                  => TwitterCldr::Js::Renderers::Implementation::Parsers::UnicodeRegex::UnicodeStringRenderer,
          :character_class                 => TwitterCldr::Js::Renderers::Implementation::Parsers::UnicodeRegex::CharacterClassRenderer,
          :character_range                 => TwitterCldr::Js::Renderers::Implementation::Parsers::UnicodeRegex::CharacterRangeRenderer,
          :character_set                   => TwitterCldr::Js::Renderers::Implementation::Parsers::UnicodeRegex::CharacterSetRenderer,
          :symbol_table                    => TwitterCldr::Js::Renderers::Implementation::Parsers::SymbolTableRenderer,
          :parser                          => TwitterCldr::Js::Renderers::Implementation::Parsers::ParserRenderer,
          :segmentation_parser             => TwitterCldr::Js::Renderers::Implementation::Parsers::SegmentationParserRenderer,
          :unicode_regex_parser            => TwitterCldr::Js::Renderers::Implementation::Parsers::UnicodeRegexParserRenderer,
          :token                           => TwitterCldr::Js::Renderers::Implementation::Tokenizers::TokenRenderer,
          :composite_token                 => TwitterCldr::Js::Renderers::Implementation::Tokenizers::CompositeTokenRenderer,
          :tokenizer                       => TwitterCldr::Js::Renderers::Implementation::Tokenizers::TokenizerRenderer,
          :segmentation_tokenizer          => TwitterCldr::Js::Renderers::Implementation::Tokenizers::SegmentationTokenizerRenderer,
          :unicode_regex_tokenizer         => TwitterCldr::Js::Renderers::Implementation::Tokenizers::UnicodeRegexTokenizerRenderer,
          :rbnf_tokenizer                  => TwitterCldr::Js::Renderers::Implementation::Tokenizers::RBNFTokenizerRenderer,
          :number_tokenizer                => TwitterCldr::Js::Renderers::Implementation::Tokenizers::NumberTokenizerRenderer,
          :pattern_tokenizer               => TwitterCldr::Js::Renderers::Implementation::Tokenizers::PatternTokenizerRenderer,
          :numbers                         => TwitterCldr::Js::Renderers::Implementation::Numbers::NumbersRenderer,
          :rbnf                            => TwitterCldr::Js::Renderers::Implementation::Numbers::RBNF::RBNFRenderer,
          :number_data_reader              => TwitterCldr::Js::Renderers::Implementation::Numbers::RBNF::NumberDataReaderRenderer,
          :rbnf_formatters                 => TwitterCldr::Js::Renderers::Implementation::Numbers::RBNF::FormattersRenderer,
          :rbnf_rule                       => TwitterCldr::Js::Renderers::Implementation::Numbers::RBNF::RuleRenderer,
          :rbnf_rule_group                 => TwitterCldr::Js::Renderers::Implementation::Numbers::RBNF::RuleGroupRenderer,
          :rbnf_rule_set                   => TwitterCldr::Js::Renderers::Implementation::Numbers::RBNF::RuleSetRenderer,
          :rbnf_substitution               => TwitterCldr::Js::Renderers::Implementation::Numbers::RBNF::SubstitutionRenderer,
          :rbnf_rule_parser                => TwitterCldr::Js::Renderers::Implementation::Numbers::RBNF::RuleParserRenderer,
          :plural                          => TwitterCldr::Js::Renderers::Implementation::Numbers::RBNF::PluralRenderer,
          :range                           => TwitterCldr::Js::Renderers::Implementation::Utils::RangeRenderer,
          :range_set                       => TwitterCldr::Js::Renderers::Implementation::Utils::RangeSetRenderer,
          :code_points                     => TwitterCldr::Js::Renderers::Implementation::Utils::CodePointsRenderer,
          :clone                           => TwitterCldr::Js::Renderers::Implementation::Clone::CloneRenderer,
        }
      end

      def data_renderers
        @data_renderers ||= {
          :settings                        => TwitterCldr::Js::Renderers::Data::Settings::SettingsRenderer,
          :plural_rules                    => TwitterCldr::Js::Renderers::Data::PluralRules::PluralRulesRenderer,
          :timespan                        => TwitterCldr::Js::Renderers::Data::Calendars::TimespanRenderer,
          :datetime                        => TwitterCldr::Js::Renderers::Data::Calendars::DateTimeRenderer,
          :break_iterator                  => TwitterCldr::Js::Renderers::Data::Shared::BreakIteratorRenderer,
          :calendar                        => TwitterCldr::Js::Renderers::Data::Shared::CalendarRenderer,
          :code_point                      => TwitterCldr::Js::Renderers::Data::Shared::CodePointRenderer,
          :lists                           => TwitterCldr::Js::Renderers::Data::Shared::ListRenderer,
          :languages                       => TwitterCldr::Js::Renderers::Data::Shared::LanguagesRenderer,
          :number_parser                   => TwitterCldr::Js::Renderers::Data::Parsers::NumberParser,
          :numbers                         => TwitterCldr::Js::Renderers::Data::Numbers::NumbersRenderer,
          :rbnf                            => TwitterCldr::Js::Renderers::Data::Numbers::RBNF::RBNFRenderer,
          :number_data_reader              => TwitterCldr::Js::Renderers::Data::Numbers::RBNF::NumberDataReaderRenderer,
        }
      end

      def test_helper_renderers
        @test_helper_renderers ||= {
          :rbnf                            => TwitterCldr::Js::Renderers::TestRenderers::TestHelpers::RBNFHelperRenderer,
          :plural_rules                    => TwitterCldr::Js::Renderers::TestRenderers::TestHelpers::PluralRulesHelperRenderer,
          :numbers                         => TwitterCldr::Js::Renderers::TestRenderers::TestHelpers::NumbersHelperRenderer
        }
      end

    end
  end
end
