require "yaml"
require 'fileutils'

require 'rubygems'
require 'json/pure'

module VMC::Cli
  class Config

    DEFAULT_TARGET  = 'api.appfog.com'

    TARGET_FILE    = '~/.af_target'
    TOKEN_FILE     = '~/.af_token'
    INSTANCES_FILE = '~/.af_instances'
    ALIASES_FILE   = '~/.af_aliases'
    CLIENTS_FILE   = '~/.af_clients'
    MICRO_FILE     = '~/.af_micro'
    CRASH_FILE     = '~/.af_crash'

    STOCK_CLIENTS = File.expand_path("../../../config/clients.yml", __FILE__)

    class << self
      attr_accessor :colorize
      attr_accessor :output
      attr_accessor :trace
      attr_accessor :nozip
      attr_accessor :infra

      def target_url
        return @target_url if @target_url
        target_file = File.expand_path(TARGET_FILE)
        if File.exists? target_file
          @target_url = lock_and_read(target_file).strip
        else
          @target_url  = DEFAULT_TARGET
        end
        @target_url = "https://#{@target_url}" unless /^https?/ =~ @target_url
        @target_url = @target_url.gsub(/\/+$/, '')
        @target_url
      end

      def base_of(url)
        url.sub(/^[^\.]+\./, "")
      end

      def store_target(target_host)
        target_file = File.expand_path(TARGET_FILE)
        lock_and_write(target_file, target_host)
      end

      def store_crash(log)
        crash_file = File.expand_path(CRASH_FILE)
        lock_and_write(crash_file, log)
      end

      def all_tokens(token_file_path=nil)
        token_file = File.expand_path(token_file_path || TOKEN_FILE)
        return nil unless File.exists? token_file
        contents = lock_and_read(token_file).strip
        JSON.parse(contents)
      end

      alias :targets :all_tokens

      def auth_token(token_file_path=nil)
        return @token if @token
        tokens = all_tokens(token_file_path)
        @token = tokens[target_url] if tokens
      end

      def remove_token_file
        FileUtils.rm_f(File.expand_path(TOKEN_FILE))
      end

      def store_token(token, token_file_path=nil)
        tokens = all_tokens(token_file_path) || {}
        tokens[target_url] = token
        token_file = File.expand_path(token_file_path || TOKEN_FILE)
        lock_and_write(token_file, tokens.to_json)
      end

      def instances
        instances_file = File.expand_path(INSTANCES_FILE)
        return nil unless File.exists? instances_file
        contents = lock_and_read(instances_file).strip
        JSON.parse(contents)
      end

      def store_instances(instances)
        instances_file = File.expand_path(INSTANCES_FILE)
        lock_and_write(instances_file, instances.to_json)
      end

      def aliases
        aliases_file = File.expand_path(ALIASES_FILE)
        # bacward compatible
        unless File.exists? aliases_file
          old_aliases_file = File.expand_path('~/.vmc-aliases')
          FileUtils.mv(old_aliases_file, aliases_file) if File.exists? old_aliases_file
        end
        aliases = YAML.load_file(aliases_file) rescue {}
      end

      def store_aliases(aliases)
        aliases_file = File.expand_path(ALIASES_FILE)
        File.open(aliases_file, 'wb') {|f| f.write(aliases.to_yaml)}
      end

      def micro
        micro_file = File.expand_path(MICRO_FILE)
        return {} unless File.exists? micro_file
        contents = lock_and_read(micro_file).strip
        JSON.parse(contents)
      end

      def store_micro(micro)
        micro_file = File.expand_path(MICRO_FILE)
        lock_and_write(micro_file, micro.to_json)
      end

      def deep_merge(a, b)
        merge = proc do |_, old, new|
          if new.is_a?(Hash) and old.is_a?(Hash)
            old.merge(new, &merge)
          else
            new
          end
        end

        a.merge(b, &merge)
      end

      def clients
        return @clients if @clients

        stock = YAML.load_file(STOCK_CLIENTS)
        clients = File.expand_path CLIENTS_FILE
        if File.exists? clients
          user = YAML.load_file(clients)
          @clients = deep_merge(stock, user)
        else
          @clients = stock
        end
      end

      def lock_and_read(file)
        File.open(file, File::RDONLY) {|f|
          if defined? JRUBY_VERSION
            f.flock(File::LOCK_SH)
          else
            f.flock(File::LOCK_EX)
          end
          contents = f.read
          f.flock(File::LOCK_UN)
          contents
        }
      end

      def lock_and_write(file, contents)
        File.open(file, File::RDWR | File::CREAT, 0600) {|f|
          f.flock(File::LOCK_EX)
          f.rewind
          f.puts contents
          f.flush
          f.truncate(f.pos)
          f.flock(File::LOCK_UN)
        }
      end
    end

    def initialize(work_dir = Dir.pwd)
      @work_dir = work_dir
    end

  end
end
