# Puppet Library
# Copyright (C) 2014 drrb
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

require 'json'
require 'zlib'
require 'open3'
require 'rubygems/package'
require 'puppet_library/forge/abstract'
require 'puppet_library/util/git'
require 'puppet_library/util/temp_dir'
require 'puppet_library/util/config_api'
require 'puppet_library/http/cache/in_memory'

module PuppetLibrary::Forge
    # A forge for serving modules from a Git repository
    #
    # <b>Usage:</b>
    #
    #    forge = PuppetLibrary::Forge::GitRepository.configure do
    #        # The location of the git repository
    #        source "http://example.com/mymodule.git"
    #
    #        # A regular expression describing which tags to serve
    #        include_tags /[0-9.]+/
    #    end
    class GitRepository < PuppetLibrary::Forge::Abstract

        def self.configure(&block)
            config_api = PuppetLibrary::Util::ConfigApi.for(GitRepository) do
                required :source, "path or URL"
                required :include_tags, "regex" do |value|
                    value.tap do |value|
                        raise "not a regex" unless value.is_a? Regexp
                    end
                end
            end

            config = config_api.configure(&block)
            cache_dir = PuppetLibrary::Util::TempDir.new("git-repo-cache")
            git = PuppetLibrary::Util::Git.new(config.get_source, cache_dir)
            GitRepository.new(git, config.get_include_tags)
        end

        # * <tt>:source</tt> - The URL or path of the git repository
        # * <tt>:version_tag_regex</tt> - A regex that describes which tags to serve
        def initialize(git, version_tag_regex)
            super(self)
            @version_tag_regex = version_tag_regex
            @git = git
            @metadata_cache = PuppetLibrary::Http::Cache::InMemory.new(60)
            @tags_cache = PuppetLibrary::Http::Cache::InMemory.new(60)
        end

        def prime
            @git.update_cache!
        end

        def clear_cache
            @git.clear_cache!
        end

        def get_module(author, name, version)
            return nil unless tags.include? tag_for(version)

            metadata = metadata_for(version)
            return nil unless metadata["name"] == "#{author}-#{name}"

            with_tag_for(version) do |tag_path|
                PuppetLibrary::Archive::Archiver.archive_dir(tag_path, "#{metadata["name"]}-#{version}") do |archive|
                    archive.add_file("metadata.json", 0644) do |entry|
                        entry.write metadata.to_json
                    end
                end
            end
        end

        def get_all_metadata
            tags.map do |tag|
                metadata_for_tag(tag)
            end
        end

        def get_metadata(author, module_name)
            metadata = get_all_metadata
            metadata.select do |m|
                m["author"] == author
                m["name"] == "#{author}-#{module_name}"
            end
        end

        private
        def tags
            @tags_cache.get do
                tags = @git.tags
                tags = tags.select {|tag| tag =~ @version_tag_regex }
                tags = tags.select do |tag|
                    @git.file_exists?("metadata.json", tag) || @git.file_exists?("Modulefile", tag)
                end
                tags.version_sort
            end
        end

        def metadata_for_tag(tag)
            @metadata_cache.get(tag) do
                metadata_file_exists = @git.file_exists?("metadata.json", tag)
                modulefile_exists = @git.file_exists?("Modulefile", tag)
                if metadata_file_exists && !modulefile_exists
                    metadata_file = @git.read_file("metadata.json", tag)
                    JSON.parse(metadata_file)
                else
                    modulefile_source = @git.read_file("Modulefile", tag)
                    modulefile = PuppetLibrary::PuppetModule::Modulefile.parse(modulefile_source)
                    modulefile.to_metadata
                end
            end
        end

        def metadata_for(version)
            metadata_for_tag(tag_for(version))
        end

        def with_tag_for(version, &block)
            @git.with_tag(tag_for(version), &block)
        end

        def tag_for(version)
            tag_versions[version]
        end

        def tag_versions
            tags_to_versions = tags.map do |tag|
                [ metadata_for_tag(tag)["version"], tag ]
            end
            Hash[tags_to_versions]
        end
    end
end
