require 'librarian/dsl'
require 'librarian/dsl/target'
require 'librarian/puppet/source'
require 'librarian/puppet/dependency'

module Librarian
  module Puppet
    class Dsl < Librarian::Dsl

      FORGE_URL = "https://forgeapi.puppetlabs.com"

      dependency :mod

      source :forge => Source::Forge
      source :git => Source::Git
      source :path => Source::Path
      source :github_tarball => Source::GitHubTarball

      def default_specfile
        Proc.new do
          forge FORGE_URL
          metadata
        end
      end

      def self.dependency_type
        Librarian::Puppet::Dependency
      end

      def post_process_target(target)
        # save the default forge defined
        default_forge = target.sources.select {|s| s.is_a? Librarian::Puppet::Source::Forge}.first
        Librarian::Puppet::Source::Forge.default = default_forge || Librarian::Puppet::Source::Forge.from_lock_options(environment, :remote => FORGE_URL)
      end

      def receiver(target)
        Receiver.new(target)
      end

      def run(specfile = nil, sources = [])
        specfile, sources = nil, specfile if specfile.kind_of?(Array) && sources.empty?

        Target.new(self).tap do |target|
          target.precache_sources(sources)
          debug_named_source_cache("Pre-Cached Sources", target)

          specfile ||= Proc.new if block_given?

          if specfile.kind_of?(Pathname) and !File.exists?(specfile)
            debug { "Specfile #{specfile} not found, using defaults" } unless specfile.nil?
            receiver(target).run(specfile, &default_specfile)
          else
            receiver(target).run(specfile)
          end

          post_process_target(target)

          debug_named_source_cache("Post-Cached Sources", target)
        end.to_spec
      end

      class Target < Librarian::Dsl::Target
        def dependency(name, *args)
          options = args.last.is_a?(Hash) ? args.pop : {}
          source = source_from_options(options) || @source
          dep = dependency_type.new(name, args, source, 'Puppetfile')
          @dependencies << dep
        end
      end

      class Receiver < Librarian::Dsl::Receiver
        attr_reader :specfile, :working_path

        # save the specfile and call librarian
        def run(specfile = nil)
          @working_path = specfile.kind_of?(Pathname) ? specfile.parent : Pathname.new(Dir.pwd)
          @specfile = specfile
          super
        end

        # implement the 'modulefile' syntax for Puppetfile
        def modulefile
          f = modulefile_path
          raise Error, "Modulefile file does not exist: #{f}" unless File.exists?(f)
          File.read(f).lines.each do |line|
            regexp = /\s*dependency\s+('|")([^'"]+)\1\s*(?:,\s*('|")([^'"]+)\3)?/
            regexp =~ line && mod($2, $4)
          end
        end

        # implement the 'metadata' syntax for Puppetfile
        def metadata
          f = working_path.join('metadata.json')
          unless File.exists?(f)
            msg = "Metadata file does not exist: #{f}"
            # try modulefile, in case we don't have a Puppetfile and we are using the default template
            if File.exists?(modulefile_path)
              modulefile
              return
            else
              raise Error, msg
            end
          end
          begin
            json = JSON.parse(File.read(f))
          rescue JSON::ParserError => e
            raise Error, "Unable to parse json file #{f}: #{e}"
          end
          dependencyList = json['dependencies']
          dependencyList.each do |d|
            mod(d['name'], d['version_requirement'])
          end
        end

        private

        def modulefile_path
          working_path.join('Modulefile')
        end
      end
    end
  end
end
