defmodule Plsm.Database.MySql do
  defstruct server: "localhost",
            port: "3306",
            username: "username",
            password: "password",
            database_name: "db",
            connection: nil
end

defimpl Plsm.Database, for: Plsm.Database.MySql do
  @doc """
    Create a MySql database struct for use in connecting to the MySql database. We pass in the configs in order to 
    properly connect
  """
  @spec create(Plsm.Database.MySql, Plsm.Configs) :: Plsm.Database.MySql
  def create(_db, configs) do
    %Plsm.Database.MySql{
      server: configs.database.server,
      port: configs.database.port,
      username: configs.database.username,
      password: configs.database.password,
      database_name: configs.database.database_name
    }
  end

  @spec connect(Plsm.Database.MySql) :: Plsm.Database.MySql
  def connect(db) do
    {_, conn} =
      MyXQL.start_link(
	protocol: :tcp,
        hostname: db.server,
        username: db.username,
        port: db.port,
        password: db.password,
        database: db.database_name
      )

    %Plsm.Database.MySql{
      connection: conn,
      server: db.server,
      port: db.port,
      username: db.username,
      password: db.password,
      database_name: db.database_name
    }
  end

  # pass in a database and then get the tables using the mariaex query then turn the rows into a table
  @spec get_tables(Plsm.Database.MySql) :: [Plsm.Database.TableHeader]
  def get_tables(db) do
    {_, result} = MyXQL.query(db.connection, "SHOW TABLES")

    result.rows
    |> List.flatten()
    |> Enum.map(fn x -> %Plsm.Database.TableHeader{database: db, name: x} end)
  end

  @spec get_columns(Plsm.Database.MySql, Plsm.Database.Table) :: [Plsm.Database.Column]
  def get_columns(db, table) do
    {_, result} = MyXQL.query(db.connection, "show columns from `#{table.name}`")

    result.rows
    |> Enum.map(&to_column/1)
  end

  defp to_column(row) do
    {_, name} = Enum.fetch(row, 0)
    type = Enum.fetch(row, 1) |> get_type
    {_, pk} = Enum.fetch(row, 3)
    primary_key? = pk == "PRI"
    %Plsm.Database.Column{name: name, type: type, primary_key: primary_key?}
  end

  defp get_type(start_type) do
    {_, type} = start_type
    downcase = String.downcase(type)

    cond do
      String.starts_with?(downcase, "int") -> :integer
      String.starts_with?(downcase, "bigint") -> :integer
      String.starts_with?(downcase, "tinyint(1)") -> :boolean
      String.starts_with?(downcase, "tinyint") -> :integer
      String.starts_with?(downcase, "bit") -> :integer
      String.contains?(downcase, "char") -> :string
      String.starts_with?(downcase, "text") -> :string
      String.starts_with?(downcase, "float") -> :float
      String.starts_with?(downcase, "double") -> :float
      String.starts_with?(downcase, "decimal") -> :decimal
      String.starts_with?(downcase, "date") -> :date
      String.starts_with?(downcase, "datetime") -> :date
      String.starts_with?(downcase, "timestamp") -> :date
      String.starts_with?(downcase, "smallint") -> :integer
      true -> :none
    end
  end
end
