module Fig; end

module Fig::Unparser
  # Determine the class of Unparser necessary for a set of Statements; the
  # parameter can be a single statement or multiple.  Returns both the class
  # and a list of explanations of why the class was picked.
  def self.class_for_statements(
    statements, emit_as_input_or_to_be_published_values
  )
    # Note: we very specifically do not require the files containing the
    # Unparser classes in order to avoid circular dependencies.
    statements = [statements].flatten

    versions =
      self.gather_versions statements, emit_as_input_or_to_be_published_values
    version = (versions.map {|version_info| version_info[0]}).max || 0
    explanations = (versions.collect {|v| v[1]}).reject {|e| e.nil?}

    case version
    when 0
      return Fig::Unparser::V0, explanations
    end

    # TODO: Until v1 grammar handling is done, ensure we don't emit anything
    # old fig versions cannot handle.
    if ! ENV['FIG_ALLOW_NON_V0_GRAMMAR']
      raise 'Reached a point where something could not be represented by the v0 grammar. Bailing out.'
    end

    return Fig::Unparser::V1, explanations
  end

  def self.determine_version_and_unparse(
    statements, emit_as_input_or_to_be_published_values
  )
    unparser_class, explanations = self.class_for_statements(
      statements, emit_as_input_or_to_be_published_values
    )
    unparser = unparser_class.new emit_as_input_or_to_be_published_values

    return (unparser.unparse [statements].flatten), explanations
  end

  private

  def self.gather_versions(statements, emit_as_input_or_to_be_published_values)
    if emit_as_input_or_to_be_published_values == :emit_as_input
      return statements.map {
        |statement|

        self.expand_version_and_explanation(
          statement, statement.minimum_grammar_for_emitting_input
        )
      }
    end

    return statements.map {
      |statement|

      self.expand_version_and_explanation(
        statement, statement.minimum_grammar_for_publishing
      )
    }
  end

  def self.expand_version_and_explanation(statement, version_info)
    version, explanation = *version_info
    if explanation.nil?
      return [version]
    end

    return [
      version,
      "Grammar v#{version} is required because the #{statement.statement_type} statement#{statement.position_string} #{explanation}."
    ]
  end


  public

  def unparse(statements)
    # It's double dispatch time!

    @text         = ''
    @indent_level = @initial_indent_level

    statements.each { |statement| statement.unparse_as_version(self) }

    text          = @text
    @text         = nil
    @indent_level = nil

    return text
  end

  def archive(statement)
    asset 'archive', statement

    return
  end

  def command(statement)
    raise NotImplementedError
  end

  def configuration(configuration_statement)
    if ! @text.empty?
      @text << "\n"
    end

    add_indent
    @text << 'config '
    @text << configuration_statement.name
    @text << "\n"

    @indent_level += 1
    begin
      configuration_statement.statements.each do
        |statement|

        statement.unparse_as_version(self)
      end
    ensure
      @indent_level -= 1
    end

    add_indent
    @text << "end\n"

    return
  end

  def grammar_version(statement)
    raise NotImplementedError
  end

  def include(statement)
    add_indent

    @text << 'include '
    @text << Fig::PackageDescriptor.format(
      statement.package_name, statement.version, statement.config_name
    )
    @text << "\n"

    return
  end

  def override(statement)
    add_indent

    @text << 'override '
    @text << Fig::PackageDescriptor.format(
      statement.package_name, statement.version, nil
    )
    @text << "\n"

    return
  end

  def path(statement)
    environment_variable(statement, 'append')

    return
  end

  def resource(statement)
    asset 'resource', statement

    return
  end

  def retrieve(statement)
    raise NotImplementedError
  end

  def set(statement)
    environment_variable(statement, 'set')

    return
  end

  def grammar_description
    raise NotImplementedError
  end

  private

  def asset(keyword, statement)
    raise NotImplementedError
  end

  def asset_path(statement)
    if @emit_as_input_or_to_be_published_values == :emit_as_input
      return statement.location
    end

    return statement.asset_name
  end

  def environment_variable(statement, keyword)
    raise NotImplementedError
  end

  def add_indent()
    @text << @indent_string * @indent_level

    return
  end
end