require 'tins'
require 'tins/xt/string'

class Utils::Config::ConfigFile
  class << self
    attr_accessor :config_file_paths
  end
  self.config_file_paths = [
    '/etc/utilsrc',
    '~/.utilsrc',
    './.utilsrc',
  ]

  include DSLKit::Interpreter

  class ConfigFileError < StandardError; end

  def initialize
  end

  def configure_from_paths(paths = self.class.config_file_paths)
    for config_file_path in paths
      parse_config_file config_file_path
    end
  end

  def parse_config_file(config_file_path)
    config_file_path = File.expand_path(config_file_path)
    File.open(config_file_path) do |cf|
      parse cf.read
    end
    self
  rescue SystemCallError => e
    $DEBUG and warn "Couldn't read config file "\
      "#{config_file_path.inspect}: #{e.class} #{e}"
    return nil
  end

  def parse(source)
    interpret_with_binding source, binding
    self
  end

  class BlockConfig
    class << self
      def inherited(modul)
        modul.extend DSLKit::DSLAccessor
        super
      end

      def config(name, *r, &block)
        self.config_settings ||= []
        config_settings << name.to_sym
        dsl_accessor name, *r, &block
        self
      end

      attr_accessor :config_settings
    end

    def initialize(&block)
      block and instance_eval(&block)
    end

    def to_ruby(depth = 0)
      result = ''
      result << ' ' * 2 * depth << "#{self.class.name[/::([^:]+)\z/, 1].underscore} do\n"
      for name in self.class.config_settings
        value = __send__(name)
        if value.respond_to?(:to_ruby)
          result << ' ' * 2 * (depth + 1) << value.to_ruby(depth + 1)
        else
          result << ' ' * 2 * (depth + 1) << "#{name} #{Array(value).map(&:inspect) * ', '}\n"
        end
      end
      result << ' ' * 2 * depth << "end\n"
    end
  end

  class Probe < BlockConfig
    config :test_framework, :'test-unit'

    config :include_dirs, %w[lib test tests ext spec]

    def include_dirs_argument
      Array(include_dirs) * ':'
    end

    def initialize(&block)
      super
      test_frameworks_allowed = [ :'test-unit', :rspec ]
      test_frameworks_allowed.include?(test_framework) or
        raise ConfigFileError,
          "test_framework has to be in #{test_frameworks_allowed.inspect}"
    end
  end

  def probe(&block)
    if block
      @probe = Probe.new(&block)
    end
    @probe ||= Probe.new
  end

  class FileFinder < BlockConfig
    def prune?(basename)
      Array(prune_dirs).any? { |pd| pd.match(basename.to_s) }
    end

    def skip?(basename)
      Array(skip_files).any? { |sf| sf.match(basename.to_s) }
    end
  end

  class Search < FileFinder
    config :prune_dirs, /\A(\.svn|\.git|CVS|tmp)\z/

    config :skip_files, /(\A\.|\.sw[pon]\z|\.log\z|~\z)/
  end

  def search(&block)
    if block
      @search = Search.new(&block)
    end
    @search ||= Search.new
  end

  class Discover < FileFinder
    config :prune_dirs, /\A(\.svn|\.git|CVS|tmp)\z/

    config :skip_files, /(\A\.|\.sw[pon]\z|\.log\z|~\z)/

    config :binary, false
  end

  def discover(&block)
    if block
      @discover = Discover.new(&block)
    end
    @discover ||= Discover.new
  end

  class StripSpaces < FileFinder
    config :prune_dirs, /\A(\..*|CVS)\z/

    config :skip_files, /(\A\.|\.sw[pon]\z|\.log\z|~\z)/
  end

  def strip_spaces(&block)
    if block
      @strip_spaces = StripSpaces.new(&block)
    end
    @strip_spaces ||= StripSpaces.new
  end

  class SshTunnel < BlockConfig
    config :terminal_multiplexer, 'screen'

    config :env, {}

    def initialize
      super
      self.terminal_multiplexer = terminal_multiplexer
    end

    def terminal_multiplexer=(terminal_multiplexer)
      @multiplexer = terminal_multiplexer.to_s
      @multiplexer =~ /\A(screen|tmux)\z/ or
        fail "invalid terminal_multiplexer #{terminal_multiplexer.inspect} was configured"
    end

    def multiplexer_list
      case @multiplexer
      when 'screen'
        'screen -ls'
      when 'tmux'
        'tmux ls'
      end
    end

    def multiplexer_new(session)
      case @multiplexer
      when 'screen'
        'false'
      when 'tmux'
        'tmux -u new -s "%s"' % session
      end
    end

    def multiplexer_attach(session)
      case @multiplexer
      when 'screen'
        'screen -DUR "%s"' % session
      when 'tmux'
        'tmux -u attach -d -t "%s"' % session
      end
    end

    class CopyPaste < BlockConfig
      config :bind_address, 'localhost'

      config :port, 6166

      config :host, 'localhost'

      config :host_port, 6166

      def to_s
        [ bind_address, port, host, host_port ] * ':'
      end
    end

    def copy_paste(enable = false, &block)
      if @copy_paste
        @copy_paste
      else
        if block
          @copy_paste = CopyPaste.new(&block)
        elsif enable
          @copy_paste = CopyPaste.new {}
        end
      end
    end
    self.config_settings << :copy_paste
  end

  def ssh_tunnel(&block)
    if block
      @ssh_tunnel = SshTunnel.new(&block)
    end
    @ssh_tunnel ||= SshTunnel.new
  end

  class Edit < BlockConfig
    config :vim_path do `which vim`.chomp end

    config :vim_default_args, nil
  end

  def edit(&block)
    if block
      @edit = Edit.new(&block)
    end
    @edit ||= Edit.new
  end

  def to_ruby
    result = "# vim: set ft=ruby:\n"
    for bc in %w[search discover strip_spaces probe ssh_tunnel]
      result << "\n" << __send__(bc).to_ruby
    end
    result
  end
end