require 'mccloud/util/ssh'
require 'erb'
require 'tempfile'

module Mccloud
  module Provisioner
    class Puppet

      attr_accessor :name
      attr_accessor :env

      attr_accessor :manifest_file
      attr_accessor :manifests_path
      attr_accessor :module_path
      attr_accessor :pp_path
      attr_accessor :options
      attr_accessor :remote_environment

      attr_reader   :server

      def initialize(env)
        @env=env
        @manifest_file = nil
        @manifests_path = "manifests"
        @module_paths = []
        @pp_path = "/tmp/mccloud-puppet"
        @name="puppet"
        @options = []
        @remote_environment={}
      end

      # Returns the manifests path expanded relative to the root path of the
      # environment.
      def expanded_manifests_path
        Pathname.new(manifests_path).expand_path(env.root_path)
      end


      # Returns the module paths as an array of paths expanded relative to the
      # root path.
      def expanded_module_paths
        return [] if !module_path

        # Get all the paths and expand them relative to the root path, returning
        # the array of expanded paths
        paths = module_path
        paths = [paths] if !paths.is_a?(Array)
        paths.map do |path|
          Pathname.new(path).expand_path(env.root_path)
        end
      end

      # Returns the manifests paths as an array of paths expanded relative to the
      # root path.
      def expanded_manifests_paths
        return [] if !manifests_path

        # Get all the paths and expand them relative to the root path, returning
        # the array of expanded paths
        paths = manifests_path
        paths = [paths] if !paths.is_a?(Array)
        paths.map do |path|
          Pathname.new(path).expand_path(env.root_path)
        end
      end

      def share_modules
        i=0
        expanded_module_paths.each do |path|
          remote_path=File.join(pp_path,"modules-#{i}")
          env.ui.info "Sharing module dir #{path}"
          server.share_sync(path,remote_path,{:mute => false})
          server.execute("chmod 700 '#{remote_path}'")
          i=i+1
        end
      end

      def share_manifests
        i=0
        expanded_manifests_paths.each do |path|
          remote_path=File.join(pp_path,"manifests-#{i}")
          env.ui.info "Sharing manifest dir #{path}"
          server.execute("test -d '#{remote_path}' || mkdir -p '#{remote_path}'")
          server.share_sync(path,remote_path,{:mute => false})
          server.execute("chmod 700 '#{remote_path}'")
          i=i+1
        end
      end

      def set_module_paths
        @module_paths = {}
        expanded_module_paths.each_with_index do |path, i|
          @module_paths[path] = File.join(@pp_path, "modules-#{i}")
        end
      end

      def share_manifest
        if @manifest_file.nil?
          # Nothing to do here
          return
        end

        full_path=Pathname.new(File.join(manifests_path,@manifest_file)).expand_path(env.root_path).to_s

        # These are the default
        dest=File.join(@pp_path,File.basename(@manifest_file))
        src=full_path

        # ERB file
        if is_erb?(full_path)
          env.ui.info "Interpreting ERB puppet manifest"

          # Correct the src path
          temp_file = Tempfile.new("puppet_erb")
          src=temp_file.path
          result=erbify(full_path)
          File.open(temp_file,'w') { |f| f.write(result)}

          # Correct the dest path
          dest=File.join(@pp_path,File.basename(@manifest_file,".erb"))

          # Immediately unlink it
          #temp_file.close(true)
        end

        env.ui.info "Synching manifest #{src} -> #{dest}"
        server.transfer(src,dest)
      end

      def is_erb?(filename)
        result=File.extname(filename) == ".erb"
        puts result
        return result
      end

      def erbify(filename)

        # Fill the array to pass to the ERB
        public_ips=Hash.new
        private_ips=Hash.new
        server.provider.vms.each do |name,vm|
          public_ips[name] = vm.public_ip_address
          private_ips[name] = vm.private_ip_address
        end

        data = { :public_ips => public_ips, :private_ips => private_ips}
        vars = ErbBinding.new(data)

        template = File.read(filename)
        erb = ERB.new(template)

        vars_binding = vars.send(:get_binding)
        result=erb.result(vars_binding)
        return result

      end

      def prepare
        share_manifests
        share_modules
        share_manifest
        set_module_paths
      end

      def cleanup
      end

      def run(server)
        @server=server
        if @manifest_file.nil?
          env.ui.info "No specific manifestfile specified defaulting to site.pp in the manifest dir"
        end

        env.logger.info "Starting puppet run"
        server.execute("mkdir -p '#{@pp_path}'")
        server.execute("chmod 700 '#{@pp_path}'")
        prepare

        env.ui.info "Running puppet"

        pre_options=[]
        unless @remote_environment.empty?
          @remote_environment.each do |key,value|
            pre_options << "#{key.to_s}=\"#{value}\""
          end
        end

        puppet_options=[ "apply"]
        @options.each do |o|
          puppet_options << o
        end


        unless @module_paths.empty?
          puppet_options << "--modulepath=#{@module_paths.values.join(':')}"
        end

        manifestdir="#{File.join(@pp_path,'manifests-0')}"
        puppet_options << "--manifestdir=#{manifestdir}"

        if @manifest_file.nil?
          puppet_options << File.join(manifestdir,"site.pp")
        else
          if is_erb?(@manifest_file)
            puppet_options << File.join(@pp_path,File.basename(@manifest_file,".erb"))
          else
            puppet_options << File.join(@pp_path,File.basename(@manifest_file))
          end
        end

        server.sudo("#{pre_options.join(" ")} puppet #{puppet_options.join(' ')}")
      end
    end #Class
  end #Module Provisioners
end #Module Mccloud
