#
#  Created by Luke Kanies on 2007-10-16.
#  Copyright (c) 2007. All rights reserved.

require 'puppet/network/authstore'
require 'puppet/util/logging'
require 'puppet/file_serving'
require 'puppet/file_serving/metadata'
require 'puppet/file_serving/content'

# Broker access to the filesystem, converting local URIs into metadata
# or content objects.
class Puppet::FileServing::Mount < Puppet::Network::AuthStore
    include Puppet::Util::Logging

    @@localmap = nil

    # Clear the cache.  This is only ever used for testing.
    def self.clear_cache
        @@localmap = nil
    end

    attr_reader :name

    # Return a new mount with the same properties as +self+, except
    # with a different name and path.
    def copy(name, path)
        result = self.clone
        result.path = path
        result.instance_variable_set(:@name, name)
        return result
    end

    # Return an instance of the appropriate class.
    def file(short_file, options = {})
        file = file_path(short_file, options[:node])

        return nil unless FileTest.exists?(file)

        return file
    end

    # Return a fully qualified path, given a short path and
    # possibly a client name.
    def file_path(relative_path, node = nil)
        full_path = path(node)
        raise ArgumentError.new("Mounts without paths are not usable") unless full_path

        # If there's no relative path name, then we're serving the mount itself.
        return full_path unless relative_path

        return File.join(full_path, relative_path)
    end

    # Create out object.  It must have a name.
    def initialize(name, path = nil)
        unless name =~ %r{^[-\w]+$}
            raise ArgumentError, "Invalid mount name format '%s'" % name
        end
        @name = name

        if path
            self.path = path
        else
            @path = nil
        end

        super()
    end

    # Return the path as appropriate, expanding as necessary.
    def path(node = nil)
        if expandable?
            return expand(@path, node)
        else
            return @path
        end
    end

    # Set the path.
    def path=(path)
        # FIXME: For now, just don't validate paths with replacement
        # patterns in them.
        if path =~ /%./
            # Mark that we're expandable.
            @expandable = true
        else
            unless FileTest.directory?(path)
                raise ArgumentError, "%s does not exist or is not a directory" % path
            end
            unless FileTest.readable?(path)
                raise ArgumentError, "%s is not readable" % path
            end
            @expandable = false
        end
        @path = path
    end

    def sync(path)
        @@syncs[path] ||= Sync.new
        @@syncs[path]
    end

    def to_s
        "mount[%s]" % @name
    end

    # Verify our configuration is valid.  This should really check to
    # make sure at least someone will be allowed, but, eh.
    def valid?
        return ! @path.nil?
    end

    private

    # LAK:FIXME Move this method to the REST terminus hook.
    def authcheck(file, client, clientip)
        raise "This method should be replaced by a REST/terminus hook"
        # If we're local, don't bother passing in information.
        if local?
            client = nil
            clientip = nil
        end
        unless mount.allowed?(client, clientip)
            mount.warning "%s cannot access %s" %
                [client, file]
            raise Puppet::AuthorizationError, "Cannot access %s" % mount
        end
    end

    # Create a map for a specific node.
    def clientmap(node)
        {
            "h" => node.sub(/\..*$/, ""), 
            "H" => node,
            "d" => node.sub(/[^.]+\./, "") # domain name
        }
    end

    # Replace % patterns as appropriate.
    def expand(path, node = nil)
        # This map should probably be moved into a method.
        map = nil

        if node
            map = clientmap(node)
        else
            Puppet.notice "No client; expanding '%s' with local host" %
                path
            # Else, use the local information
            map = localmap()
        end

        path.gsub(/%(.)/) do |v|
            key = $1
            if key == "%" 
                "%"
            else
                map[key] || v
            end
        end
    end

    # Do we have any patterns in our path, yo?
    def expandable?
        if defined? @expandable
            @expandable
        else
            false
        end
    end

    # Cache this manufactured map, since if it's used it's likely
    # to get used a lot.
    def localmap
        unless @@localmap
            @@localmap = {
                "h" =>  Facter.value("hostname"),
                "H" => [Facter.value("hostname"),
                        Facter.value("domain")].join("."),
                "d" =>  Facter.value("domain")
            }
        end
        @@localmap
    end
end
