defmodule Mix.Tasks.Ecto.Dump do
  use Mix.Task
  import Mix.Ecto
  import Mix.EctoSQL

  @shortdoc "Dumps the repository database structure"
  @default_opts [quiet: false]

  @aliases [
    d: :dump_path,
    q: :quiet,
    r: :repo

  @switches [
    dump_path: :string,
    quiet: :boolean,
    repo: [:string, :keep],
    no_compile: :boolean,
    no_deps_check: :boolean,
    prefix: [:string, :keep]

  @moduledoc """
  Dumps the current environment's database structure for the
  given repository into a structure file.

  The repository must be set under `:ecto_repos` in the
  current app configuration or given via the `-r` option.

  This task needs some shell utility to be present on the machine
  running the task.

   Database   | Utility needed
   :--------- | :-------------
   PostgreSQL | pg_dump
   MySQL      | mysqldump

  ## Example

      $ mix ecto.dump

  ## Command line options

    * `-r`, `--repo` - the repo to load the structure info from
    * `-d`, `--dump-path` - the path of the dump file to create
    * `-q`, `--quiet` - run the command quietly
    * `--no-compile` - does not compile applications before dumping
    * `--no-deps-check` - does not check dependencies before dumping
    * `--prefix` - prefix that will be included in the structure dump.
      Can include multiple prefixes (ex. --prefix foo --prefix bar).
      When specified, the prefixes will have their definitions dumped along
      with the data in their migration table. The default behavior is
      dependent on the adapter for backwards compatibility reasons.
      For PostgreSQL, the configured database has the definitions dumped
      from all of its schemas but only the data from the migration table
      from the `public` schema is included. For MySQL, only the configured
      database and its migration table are dumped.

  @impl true
  def run(args) do
    {opts, _} = OptionParser.parse! args, strict: @switches, aliases: @aliases

    dump_prefixes =
      case Keyword.get_values(opts, :prefix) do
        [_ | _] = prefixes -> prefixes
        [] -> nil

    opts = @default_opts
    |> Keyword.merge(opts)
    |> Keyword.put(:dump_prefixes, dump_prefixes)

    Enum.each parse_repo(args), fn repo ->
      ensure_repo(repo, args)
      ensure_implements(repo.__adapter__(), Ecto.Adapter.Structure,
                                            "dump structure for #{inspect repo}")

      migration_repo = repo.config()[:migration_repo] || repo

      for repo <- Enum.uniq([repo, migration_repo]) do
        config = Keyword.merge(repo.config(), opts)

        case repo.__adapter__().structure_dump(source_repo_priv(repo), config) do
          {:ok, location} ->
            unless opts[:quiet] do
     "The structure for #{inspect repo} has been dumped to #{location}"
          {:error, term} when is_binary(term) ->
            Mix.raise "The structure for #{inspect repo} couldn't be dumped: #{term}"
          {:error, term} ->
            Mix.raise "The structure for #{inspect repo} couldn't be dumped: #{inspect term}"