require "yaml"
require "pp"

module Fontcustom
  class Options
    include Utility

    def initialize(cli_options = {})
      @manifest = cli_options[:manifest]
      @cli_options = symbolize_hash(cli_options)
      parse_options
    end

    private

    def parse_options
      overwrite_examples
      set_config_path
      load_config
      merge_options
      clean_font_name
      clean_css_selector
      set_input_paths
      set_output_paths
      check_template_paths
      print_debug if @options[:debug]
    end

    # We give Thor fake defaults to generate more useful help messages.
    # Here, we delete any CLI options that match those examples.
    # TODO There's *got* a be a cleaner way to customize Thor help messages.
    def overwrite_examples
      EXAMPLE_OPTIONS.keys.each do |key|
        @cli_options.delete(key) if @cli_options[key] == EXAMPLE_OPTIONS[key]
      end
      @cli_options = DEFAULT_OPTIONS.dup.merge @cli_options
    end

    def set_config_path
      @cli_options[:config] = if @cli_options[:config]
        path = @cli_options[:config]
        if File.exists?(path) && ! File.directory?(path)
          path
        elsif File.exists? File.join(path, "fontcustom.yml")
          File.join path, "fontcustom.yml"
        else
          raise Fontcustom::Error, "No configuration file found at `#{path}`."
        end
      else
        if File.exists? "fontcustom.yml"
          "fontcustom.yml"
        elsif File.exists? File.join("config", "fontcustom.yml")
          File.join "config", "fontcustom.yml"
        else
          false
        end
      end
    end

    def load_config
      @config_options = {}
      if @cli_options[:config]
        begin
          config = YAML.load File.open(@cli_options[:config])
          if config # empty YAML returns false
            @config_options = symbolize_hash(config)
            say_message :debug, "Using settings from `#{@cli_options[:config]}`." if @cli_options[:debug] || @config_options[:debug]
          else
            say_message :warn, "`#{@cli_options[:config]}` was empty. Using defaults."
          end
        rescue Exception => e
          raise Fontcustom::Error, "Error parsing `#{@cli_options[:config]}`:\n#{e.message}"
        end
      end
    end

    # TODO validate keys
    def merge_options
      @cli_options.delete_if { |key, val| val == DEFAULT_OPTIONS[key] }
      @options = DEFAULT_OPTIONS.merge(@config_options).merge(@cli_options)
      @options.delete :manifest
    end

    def clean_font_name
      @options[:font_name] = @options[:font_name].strip.gsub(/\W/, "-")
    end

    def clean_css_selector
      unless @options[:css_selector].include? "{{glyph}}"
        raise Fontcustom::Error,
          "CSS selector `#{@options[:css_selector]}` should contain the \"{{glyph}}\" placeholder."
      end
      @options[:css_selector] = @options[:css_selector].strip.gsub(/[^&%=\[\]\.#\{\}""\d\w]/, "-")
    end

    def set_input_paths
      if @options[:input].is_a? Hash
        @options[:input] = symbolize_hash(@options[:input])
        if @options[:input].has_key? :vectors
          check_input @options[:input][:vectors]
        else
          raise Fontcustom::Error,
            "Input paths (assigned as a hash) should have a :vectors key. Check your options."
        end

        if @options[:input].has_key? :templates
          check_input @options[:input][:templates]
        else
          @options[:input][:templates] = @options[:input][:vectors]
        end
      else
        if @options[:input]
          input = @options[:input]
        else
          input = "."
          say_message :warn, "No input directory given. Using present working directory."
        end
        check_input input
        @options[:input] = { :vectors => input, :templates => input }
      end

      if Dir[File.join(@options[:input][:vectors], "*.svg")].empty?
        raise Fontcustom::Error, "`#{@options[:input][:vectors]}` doesn't contain any SVGs."
      end
    end

    def set_output_paths
      if @options[:output].is_a? Hash
        @options[:output] = symbolize_hash(@options[:output])
        unless @options[:output].has_key? :fonts
          raise Fontcustom::Error,
            "Output paths (assigned as a hash) should have a :fonts key. Check your options."
        end

        @options[:output].each do |key, val|
          @options[:output][key] = val
          if File.exists?(val) && ! File.directory?(val)
            raise Fontcustom::Error,
              "Output `#{@options[:output][key]}` exists but isn't a directory. Check your options."
          end
        end

        @options[:output][:css] ||= @options[:output][:fonts]
        @options[:output][:preview] ||= @options[:output][:fonts]
      else
        if @options[:output].is_a? String
          output = @options[:output]
          if File.exists?(output) && ! File.directory?(output)
            raise Fontcustom::Error,
              "Output `#{output}` exists but isn't a directory. Check your options."
          end
        else
          output = @options[:font_name]
          say_message :debug, "Generated files will be saved to `#{output}/`." if @options[:debug]
        end

        @options[:output] = {
          :fonts => output,
          :css => output,
          :preview => output
        }
      end
    end

    def check_template_paths
      @options[:templates].each do |template|
        next if %w|preview css scss scss-rails|.include? template
        if template[0] == "/"
          path = template
        else
          path = File.expand_path File.join(@options[:input][:templates], template)
        end
        unless File.exists? path
          raise Fontcustom::Error,
            "Custom template `#{template}` wasn't found in `#{@options[:input][:templates]}/`. Check your options."
        end
      end
    end

    def check_input(dir)
      if ! File.exists? dir
        raise Fontcustom::Error,
          "Input `#{dir}` doesn't exist. Check your options."
      elsif ! File.directory? dir
        raise Fontcustom::Error,
          "Input `#{dir}` isn't a directory. Check your options."
      end
    end

    def print_debug
      message = line_break(16)
      message << @options.pretty_inspect.split("\n ").join(line_break(16))
      say_message :debug, "Using options:#{message}"
    end
  end
end
