require 'boson/namespacer'
require 'boson/repo'
require 'boson/index'
require 'boson/repo_index'

module Boson
  module Save
    def config
      repo.config
    end

    def config=(val)
      repo.config = val
    end

    # The main required repository which defaults to ~/.boson.
    def repo
      @repo ||= Repo.new("#{ENV['BOSON_HOME'] || Dir.home}/.boson")
    end

    # An optional local repository which defaults to ./lib/boson or ./.boson.
    def local_repo
      @local_repo ||= begin
        ignored_dirs = (config[:ignore_directories] || []).map {|e| File.expand_path(e) }
        dir = ["lib/boson", ".boson"].find {|e| File.directory?(e) &&
            File.expand_path(e) != repo.dir && !ignored_dirs.include?(File.expand_path('.')) }
        Repo.new(dir) if dir
      end
    end

    # The array of loaded repositories containing the main repo and possible local and global repos
    def repos
      @repos ||= [repo, local_repo, global_repo].compact
    end

    # Optional global repository at /etc/boson
    def global_repo
      File.exists?('/etc/boson') ? Repo.new('/etc/boson') : nil
    end
  end
  extend Save

  class BareRunner
    module Save
      def init
        add_load_path
        super
      end

      def autoload_command(cmd, opts={verbose: Boson.verbose})
        Index.read
        (lib = Index.find_library(cmd)) && Manager.load(lib, opts)
        lib
      end

      def define_autoloader
        class << ::Boson.main_object
          def method_missing(method, *args, &block)
            if BareRunner.autoload_command(method.to_s)
              send(method, *args, &block) if respond_to?(method)
            else
              super
            end
          end
        end
      end

      def default_libraries
        Boson.repos.map {|e| e.config[:defaults] || [] }.flatten + super
      end

      # Libraries detected in repositories
      def detected_libraries
        Boson.repos.map {|e| e.detected_libraries }.flatten.uniq
      end

      # Libraries specified in config files and detected_libraries
      def all_libraries
        Boson.repos.map {|e| e.all_libraries }.flatten.uniq
      end

      def add_load_path
        Boson.repos.each {|repo|
          if repo.config[:add_load_path] || File.exists?(File.join(repo.dir, 'lib'))
            $: <<  File.join(repo.dir, 'lib') unless $:.include? File.expand_path(File.join(repo.dir, 'lib'))
          end
        }
      end
    end
    extend Save
  end

  # * All libraries can be configured by passing a hash of {library attributes}[link:classes/Boson/Library.html#M000077] under
  #   {the :libraries key}[link:classes/Boson/Repo.html#M000070] to the main config file ~/.boson/config/boson.yml.
  #   For most libraries this may be the only way to configure a library's commands.
  #   An example of a GemLibrary config:
  #    :libraries:
  #      httparty:
  #       :class_commands:
  #         delete: HTTParty.delete
  #       :commands:
  #         delete:
  #           :alias: d
  #           :desc: Http delete a given url
  #
  # When installing a third-party library, use the config file as a way to override default library and command attributes
  # without modifying the library.
  class Library
    module Save
      attr_accessor :repo_dir

      def before_initialize
        @repo_dir = set_repo.dir
      end

      def config
        set_repo.config
      end

      def set_repo
        Boson.repo
      end

      def marshal_dump
        [@name, @commands, @gems, @module.to_s, @repo_dir, @indexed_namespace]
      end

      def marshal_load(ary)
        @name, @commands, @gems, @module, @repo_dir, @indexed_namespace = ary
      end
    end
    include Save
  end

  class Command
    module Save
      def marshal_dump
        if @args && @args.any? {|e| e[1].is_a?(Module) }
          @args.map! {|e| e.size == 2 ? [e[0], e[1].inspect] : e }
          @file_parsed_args = true
        end
        [@name, @alias, @lib, @desc, @options, @render_options, @args, @default_option]
      end

      def marshal_load(ary)
        @name, @alias, @lib, @desc, @options, @render_options, @args, @default_option = ary
      end
    end
    include Save
  end

  if defined? BinRunner
  # Any changes to your commands are immediately available from
  # the commandline except for changes to the main config file.  For those
  # changes to take effect you need to explicitly load and index the libraries
  # with --index.  See RepoIndex to understand how Boson can immediately detect
  # the latest commands.
  class BinRunner
    module Save
      def autoload_command(cmd)
        if !Boson.can_invoke?(cmd, false)
          update_index
          super(cmd, load_options)
        end
      end

      def update_index
        Index.update(verbose: verbose)
      end

      def execute_command(cmd, args)
        @command = cmd # for external errors
        autoload_command cmd
        super
      end

      def command_not_found?(cmd)
        super && (!(Index.read && Index.find_command(cmd[/\w+/])) || cmd.include?(NAMESPACE))
      end

      def command_name(cmd)
        cmd.split(Boson::NAMESPACE)[-1]
      end

      def eval_execute_option(str)
        define_autoloader
        super
      end

      def default_libraries
        super + Boson.repos.map {|e| e.config[:bin_defaults] || [] }.flatten +
          Dir.glob('Bosonfile')
      end
    end
    extend Save
  end
  end
end