# frozen_string_literal: true

require "pathname"

# Public: A Class containing all the metadata and utilities needed to manage a
# ruby project.
class ThisProject
  # The name of this project
  attr_accessor :name

  # The author's name
  attr_accessor :author

  # The email address of the author(s)
  attr_accessor :email

  # The homepage of this project
  attr_accessor :homepage

  # The regex of files to include in the manifest
  attr_accessor :include_in_manifest

  # The hash of Gem::Specifications keyed' by platform
  attr_accessor :gemspecs

  # List of cross platforms to build the gem for
  attr_accessor :cross_platforms

  # Public: Initialize ThisProject
  #
  # Yields self
  def initialize
    @include_in_manifest = Regexp.union(/\Alib/, /\Aexe/, /\Aext/,
                                        %r{\A[^/]+\.(gemspec|txt|md|rdoc|adoc)\Z})
    @gemspecs = {}
    yield self if block_given?
  end

  # Public: return the version of ThisProject
  #
  # Search the ruby files in the project looking for the one that has the
  # version string in it. This does not eval any code in the project, it parses
  # the source code looking for the string.
  #
  # Returns a String version
  def version
    ["lib/#{name}.rb", "lib/#{name}/version.rb"].each do |v|
      path = project_path(v)
      line = path.read[/^\s*VERSION\s*=\s*.*/]
      return line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1] if line
    end
  end

  # Internal: Return a section of an RDoc file with the given section name
  #
  # path         - the relative path in the project of the file to parse
  # section_name - the section out of the file from which to parse data
  #
  # Retuns the text of the section as an array of paragrphs.
  def section_of(file, section_name)
    re = /^[=#]+ (.*)$/
    sectional = project_path(file)
    parts = sectional.read.split(re)[1..]
    parts.map!(&:strip)

    sections = {}
    Hash[*parts].each do |k, v|
      sections[k] = v.split("\n\n")
    end
    sections[section_name]
  end

  # Internal: print out a warning about the give task
  def task_warning(task)
    warn "WARNING: '#{task}' tasks are not defined. Please run 'bin/setup'"
  end

  # Internal: Return the full path to the file that is relative to the project
  # root.
  #
  # path - the relative path of the file from the project root
  #
  # Returns the Pathname of the file
  def project_path(*relative_path)
    project_root.join(*relative_path)
  end

  # Internal: The absolute path of this file
  #
  # Returns the Pathname of this file.
  def this_file_path
    Pathname.new(__FILE__).expand_path
  end

  # Internal: The root directory of this project
  #
  # This is defined as being the directory that is in the path of this project
  # that has the first Rakefile
  #
  # Returns the Pathname of the directory
  def project_root
    this_file_path.ascend do |p|
      rakefile = p.join("Rakefile")
      return p if rakefile.exist?
    end
  end

  # Internal: Returns the contents of the Manifest.txt file as an array
  #
  # Returns an Array of strings
  def manifest
    manifest_file = project_path("Manifest.txt")
    abort "You need a Manifest.txt" unless manifest_file.readable?
    manifest_file.readlines.map(&:strip)
  end

  # Internal: Return the files that define the extensions
  #
  # Returns an Array
  def extension_conf_files
    manifest.grep(/extconf.rb\Z/)
  end

  # Internal: Returns the gemspace associated with the current ruby platform
  def platform_gemspec
    gemspecs.fetch(platform) { This.ruby_gemspec }
  end

  def core_gemspec
    Gem::Specification.new do |spec|
      spec.name        = name
      spec.version     = version
      spec.author      = author
      spec.email       = email
      spec.homepage    = homepage

      spec.summary     = summary
      spec.description = description
      spec.license     = license

      spec.files       = manifest
      spec.bindir      = "exe"
      spec.executables = spec.files.grep(/^exe/) { |f| File.basename(f) }
      spec.test_files  = []

      spec.extra_rdoc_files += spec.files.grep(/(txt|rdoc|md)$/)
      spec.rdoc_options = ["--main", "README.md",
                           "--markup", "tomdoc",]

      spec.required_ruby_version = ">= 2.3.0"
    end
  end

  # Internal: Return the gemspec for the ruby platform
  def ruby_gemspec(core = core_gemspec, &)
    yielding_gemspec("ruby", core, &)
  end

  # Internal: Return the gemspec for the jruby platform
  def java_gemspec(core = core_gemspec, &)
    yielding_gemspec("java", core, &)
  end

  # Internal: give an initial spec and a key, create a new gemspec based off of
  # it.
  #
  # This will force the new gemspecs 'platform' to be that of the key, since the
  # only reason you would have multiple gemspecs at this point is to deal with
  # different platforms.
  def yielding_gemspec(key, core)
    spec = gemspecs[key] ||= core.dup
    spec.platform = key
    yield spec if block_given?
    spec
  end

  # Internal: Return the platform of ThisProject at the current moment in time.
  def platform
    (RUBY_PLATFORM == "java") ? "java" : Gem::Platform::RUBY
  end

  # Internal: Return the DESCRIPTION section of the README.rdoc file
  def description_section
    section_of("README.md", "DESCRIPTION")
  end

  # Internal: Return the summary text from the README
  def summary
    description_section.first
  end

  # Internal: Return the full description text from the README
  def description
    description_section.join(" ").tr("\n", " ").gsub(/[{}]/, "").gsub(/\[[^\]]+\]/, "") # strip rdoc
  end

  def license
    license_file = project_path("LICENSE.txt")
    line = license_file.readlines.first
    line.split(/\s+/).first
  end

  # Internal: The path to the gemspec file
  def gemspec_file
    project_path("#{name}.gemspec")
  end
end

This = ThisProject.new
