require 'generators/ember/generator_helpers'
require 'net/http'
require 'uri'
require 'fileutils'

module Ember
  module Generators
    class InstallGenerator < ::Rails::Generators::Base

      class InvalidChannel < ::Thor::Error; end
      class ConflictingOptions < ::Thor::Error; end
      class InsufficientOptions < ::Thor::Error; end

      ::InvalidChannel = InvalidChannel
      ::ConflictingOptions = ConflictingOptions
      ::InsufficientOptions = InsufficientOptions

      desc "Install Ember.js into your vendor folder"
      class_option :head,
        :type => :boolean,
        :default => false,
        :desc => "Download Ember.js & Ember data from canary channel. This is deprecated. Use channel instead."
      class_option :channel,
        :type => :string,
        :required => false,
        :desc => "Ember release channel Choose between 'release', 'beta' or 'canary'"
      class_option :ember_only,
        :type => :boolean,
        :required => false,
        :desc => "Only download Ember.",
        :aliases => '--ember'
      class_option :ember_data_only,
        :type => :boolean,
        :required => false,
        :desc => "Only download ember-data",
        :aliases => '--ember-data'
      class_option :tag,
        :type => :string,
        :required => false,
        :desc => "Download tagged release use syntax v1.0.0-beta.3/ember-data & v1.0.0-rc.8/ember"

      def initialize(args = [], options = {}, config = {})
        super(args, options, config)
        check_options
        process_options
      end

      def ember
        begin
          unless options.ember_data_only?
            get_ember_js_for(:development)
            get_ember_js_for(:production)
          end
        rescue Thor::Error
          say('WARNING: no ember files on this channel or tag' , :yellow)
        end
      end

      def ember_data
        begin
          unless options.ember_only?
            get_ember_data_for(:development)
            get_ember_data_for(:production)
          end
        rescue Thor::Error
          say('WARNING: no ember-data files on this channel or tag' , :yellow)
        end
      end

      private

      def get_ember_data_for(environment)

        create_file "vendor/assets/ember/#{environment}/ember-data.js" do
          fetch url_for(channel, 'ember-data', environment), "vendor/assets/ember/#{environment}/ember-data.js"
        end

        sourcemap_url = "#{base_url}/#{channel}/ember-data.js.map"
        if resource_exist?(sourcemap_url)
          create_file "vendor/assets/ember/#{environment}/ember-data.js.map" do
            fetch sourcemap_url, "vendor/assets/ember/#{environment}/ember-data.js.map", false
          end
        end
      end

      def get_ember_js_for(environment)
        create_file "vendor/assets/ember/#{environment}/ember.js" do
          fetch url_for(channel, 'ember', environment), "vendor/assets/ember/#{environment}/ember.js"
        end

        compiler_url = "#{base_url}/#{channel}/ember-template-compiler.js"
        if resource_exist?(compiler_url)
          create_file "vendor/assets/ember/#{environment}/ember-template-compiler.js" do
            fetch "#{base_url}/#{channel}/ember-template-compiler.js", "vendor/assets/ember/#{environment}/ember-template-compiler.js"
          end
        end
      end

      def url_for(channel, component, environment)
        base = "#{base_url}/#{channel}/#{component}"

        case environment
        when :production
          "#{base}.prod.js"
        when :development
          if resource_exist?("#{base}.debug.js")
            "#{base}.debug.js" # Ember.js 1.10.0.beta.1 or later
          else
            "#{base}.js"
          end
        end
      end

      def check_options
        if options.head?
          say('WARNING: --head option is deprecated in favor of --channel=canary' , :yellow)
        end
        if options.head? && options.channel?
          say 'ERROR: conflicting options. --head and --channel. Use either --head or --channel=<channel>', :red
          raise ConflictingOptions
        end
        if options.channel? && !%w(release beta canary).include?(options[:channel])
          say 'ERROR: channel can either be release, beta or canary', :red
          raise InvalidChannel
        end
        if options.channel? && options.tag?
          say 'ERROR: conflicting options. --tag and --channel. --tag is incompatible with other options', :red
          raise ConflictingOptions
        end
        if options.head? && options.tag?
          say 'ERROR: conflicting options. --tag and --head. --tag is incompatible with other options', :red
          raise ConflictingOptions
        end
        if options.tag? && !(options.ember_only? || options.ember_data_only?)
          say 'ERROR: insufficient options. --tag needs to be combined with eithe --ember or --ember-data', :red
          raise InsufficientOptions
        end
      end

      def process_options
        if options.head?
          @channel = :canary
        end
        if options.tag?
          @channel = "tags/#{options.tag}"
        end
      end

      def base_url
        'http://builds.emberjs.com'
      end

      def channel
        if options.channel
          @channel ||= options[:channel]
        else
          @channel ||= :release
        end
      end

      def fetch(from, to, prepend_verbose = true)
        message = "#{from} -> #{to}"
        say_status("downloading:", message , :green)

        uri = URI(from)
        output = StringIO.new
        if prepend_verbose
          output.puts "// Fetched from channel: #{channel}, with url " + uri.to_s
          output.puts "// Fetched on: " + Time.now.utc.iso8601.to_s
        end

        response = Net::HTTP.get_response(uri)
        case response.code
        when '404'
          say "ERROR: Error reading the content from the channel with url #{from}. File not found" , :red
          raise Thor::Error
        when '200'
          output.puts response.body.force_encoding("UTF-8")
        else
          say "ERROR: Unexpected error with status #{response.code} reading the content from the channel with url #{from}." , :red
          raise Thor::Error
        end
        output.rewind
        output.read
      end

      def resource_exist?(target)
        uri = URI(target)
        response = Net::HTTP.new(uri.host, uri.port).head(uri.path)
        response.code == '200'
      end
    end
  end
end
