lib/reference.ex

defmodule BibleEx.Reference do
  @moduledoc """
  Is a general Bible reference struct.
  """

  alias BibleEx.Librarian
  alias BibleEx.Chapter
  alias BibleEx.Verse

  @enforce_keys [:book]

  defstruct book: "",
            book_names: %{},
            book_number: nil,
            reference: nil,
            reference_type: nil,
            start_chapter: nil,
            start_chapter_number: nil,
            start_verse: nil,
            start_verse_number: nil,
            end_chapter: nil,
            end_chapter_number: nil,
            end_verse: nil,
            end_verse_number: nil,
            is_valid: false,
            chapters: [],
            verses: [],
            osis: nil,
            abbr: nil,
            short: nil

  @doc """
  Make a new `%Reference{}` struct.

  ## Parameters

    - *book*: a string that is one of the four possible book name formats
    - `"GEN"` (abbr)
    - `"Genesis"` (name)
    - `"Gen"` (osis)
    - `"Gn"` (short)
    - *start_chapter*: an integer that is the start chapter number
    - *start_verse*: an integer that is the start verse number
    - *end_chapter*: an integer that is the end chapter number
    - *end_verse*: an integer that is the end verse number

  ## Examples
      iex> alias BibleEx.Reference
      iex> genesis_ref = Reference.new(book: "Genesis", start_chapter: 2, start_verse: 3, end_chapter: 4, end_verse: 5)

      %BibleEx.Reference{
        book: "Genesis",
        book_names: %{abbr: "GEN", name: "Genesis", osis: "Gen", short: "Gn"},
        book_number: 1,
        reference: "Genesis 2:3 - 4:5",
        reference_type: :chapter_range,
        start_chapter: %BibleEx.Chapter{
          ...
        }
      }

      iex> alias BibleEx.Reference
      iex> Reference.new(book: "3 John")

      %BibleEx.Reference{
        book: "3 John",
        book_names: %{abbr: "3JO", name: "3 John", osis: "3John", short: "3 Jn"},
        book_number: 64,
        reference: "3 John",
        reference_type: :chapter,
        start_chapter: %BibleEx.Chapter{
          book: "3 John",
          book_names: %{abbr: "3JO", name: "3 John", osis: "3John", short: "3 Jn"},
          reference_type: :chapter,
          reference: "3 John 1",
          chapter_number: 1,
          start_verse_number: 1,
          start_verse: %BibleEx.Verse{
            book: "3 John",
            book_names: %{abbr: "3JO", name: "3 John", osis: "3John", short: "3 Jn"},
            book_number: 64,
            reference_type: :verse,
            reference: "3 John 1:1",
            chapter_number: 1,
            verse_number: 1,
            is_valid: true
          },
          end_verse: %BibleEx.Verse{
            book: "3 John",
            book_names: %{abbr: "3JO", name: "3 John", osis: "3John", short: "3 Jn"},
            book_number: 64,
            reference_type: :verse,
            reference: "3 John 1:15",
            chapter_number: 1,
            verse_number: 15,
            is_valid: true
          },
          end_verse_number: 15,
          verses: [
            ...
          ],
          is_valid: true
        },
        start_chapter_number: 1,
        start_verse: %BibleEx.Verse{
          book: "3 John",
          book_names: %{abbr: "3JO", name: "3 John", osis: "3John", short: "3 Jn"},
          book_number: 64,
          reference_type: :verse,
          reference: "3 John 1:1",
          chapter_number: 1,
          verse_number: 1,
          is_valid: true
        },
        start_verse_number: 1,
        end_chapter: %BibleEx.Chapter{
          book: "3 John",
          book_names: %{abbr: "3JO", name: "3 John", osis: "3John", short: "3 Jn"},
          reference_type: :chapter,
          reference: nil,
          chapter_number: nil,
          start_verse_number: 1,
          start_verse: %BibleEx.Verse{
            book: "3 John",
            book_names: %{abbr: "3JO", name: "3 John", osis: "3John", short: "3 Jn"},
            book_number: 64,
            reference_type: :verse,
            reference: nil,
            chapter_number: nil,
            verse_number: 1,
            is_valid: false
          },
          end_verse: %BibleEx.Verse{
            book: "3 John",
            book_names: %{abbr: "3JO", name: "3 John", osis: "3John", short: "3 Jn"},
            book_number: 64,
            reference_type: :verse,
            reference: "3 John 1:15",
            chapter_number: 1,
            verse_number: 15,
            is_valid: true
          },
          end_verse_number: 15,
          verses: nil,
          is_valid: true
        },
        end_chapter_number: 1,
        end_verse: 15,
        end_verse_number: 15,
        is_valid: true,
        chapters: [
          %BibleEx.Chapter{
            book: "3 John",
            book_names: %{abbr: "3JO", name: "3 John", osis: "3John", short: "3 Jn"},
            reference_type: :chapter,
            reference: "3 John 1",
            chapter_number: 1,
            start_verse_number: 1,
            start_verse: %BibleEx.Verse{
              book: "3 John",
              book_names: %{abbr: "3JO", name: "3 John", osis: "3John", short: "3 Jn"},
              book_number: 64,
              reference_type: :verse,
              reference: "3 John 1:1",
              chapter_number: 1,
              verse_number: 1,
              is_valid: true
            },
            end_verse: %BibleEx.Verse{
              book: "3 John",
              book_names: %{abbr: "3JO", name: "3 John", osis: "3John", short: "3 Jn"},
              book_number: 64,
              reference_type: :verse,
              reference: "3 John 1:15",
              chapter_number: 1,
              verse_number: 15,
              is_valid: true
            },
            end_verse_number: 15,
            verses: [
              ...
            ],
            is_valid: true
          }
        ],
        verses: [
          ...
        ]
      }

  """

  def new(book: book) do
    new(book: book, start_chapter: nil, start_verse: nil, end_chapter: nil, end_verse: nil)
  end

  def new(
        book: book,
        start_chapter: start_chapter
      ) do
    new(
      book: book,
      start_chapter: start_chapter,
      start_verse: nil,
      end_chapter: nil,
      end_verse: nil
    )
  end

  def new(
        book: book,
        start_chapter: start_chapter,
        start_verse: start_verse
      ) do
    new(
      book: book,
      start_chapter: start_chapter,
      start_verse: start_verse,
      end_chapter: nil,
      end_verse: nil
    )
  end

  def new(
        book: book,
        start_chapter: start_chapter,
        start_verse: start_verse,
        end_chapter: end_chapter
      ) do
    new(
      book: book,
      start_chapter: start_chapter,
      start_verse: start_verse,
      end_chapter: end_chapter,
      end_verse: nil
    )
  end

  def new(
        book: book,
        start_chapter: start_chapter,
        start_verse: start_verse,
        end_chapter: end_chapter,
        end_verse: end_verse
      ) do
    bname =
      case Librarian.get_book_names(book: book) do
        book_names = %{
          osis: _osis,
          abbr: _abbr,
          name: _name,
          short: _short
        } ->
          Map.get(book_names, :name, book)

        %{} ->
          book
      end

    sc =
      if !is_nil(start_chapter) do
        Chapter.new(book: bname, chapter_number: start_chapter)
      else
        Chapter.new(book: bname, chapter_number: 1)
      end

    scn =
      if !is_nil(start_chapter) do
        start_chapter
      else
        1
      end

    ec =
      if !is_nil(end_chapter) do
        Chapter.new(book: bname, chapter_number: end_chapter)
      else
        if !is_nil(start_chapter) do
          Chapter.new(book: bname, chapter_number: start_chapter)
        else
          Chapter.new(book: bname, chapter_number: end_chapter)
        end
      end

    ecn =
      if !is_nil(end_chapter) do
        end_chapter
      else
        if !is_nil(start_chapter) do
          scn
        else
          Librarian.get_last_chapter_number(book: bname)
        end
      end

    sv =
      if !is_nil(start_verse) do
        Verse.new(book: bname, chapter_number: start_chapter, verse_number: start_verse)
      else
        Verse.new(book: bname, chapter_number: 1, verse_number: 1)
      end

    svn =
      if !is_nil(start_verse) do
        start_verse
      else
        1
      end

    ev =
      if !is_nil(end_verse) do
        Verse.new(book: bname, chapter_number: scn, verse_number: end_verse)
      else
        if !is_nil(start_verse) do
          Verse.new(book: bname, chapter_number: scn, verse_number: start_verse)
        else
          Librarian.get_last_verse_number(book: bname, chapter: scn)
        end
      end

    evn =
      if !is_nil(end_verse) do
        end_verse
      else
        if !is_nil(start_verse) do
          start_verse
        else
          Librarian.get_last_verse_number(book: bname, chapter: ecn)
        end
      end

    chapters =
      Librarian.get_chapters(
        book: bname,
        start_chapter: scn,
        end_chapter: ecn
      )

    chapter_list =
      scn..ecn
      |> Enum.to_list()

    verse_collection =
      Enum.map(chapter_list, fn x ->
        # if svn is nil then choose 1, else choose svn
        find_start_verse =
          if !is_nil(svn) and x == Enum.at(chapter_list, 0) do
            svn
          else
            1
          end

        # if evn is nil then choose last verse, else choose evn
        find_last_verse =
          if !is_nil(evn) and x == List.last(chapter_list) do
            evn
          else
            Librarian.get_last_verse_number(book: bname, chapter: x)
          end

        Librarian.get_verses(
          book: bname,
          chapter: x,
          start_verse: find_start_verse,
          end_verse: find_last_verse
        )
      end)

    book_names = Librarian.get_book_names(book: bname)
    osis_book = Map.get(Librarian.get_book_names(book: bname), :osis, nil)
    abbr_book = Map.get(Librarian.get_book_names(book: bname), :abbr, nil)
    short_book = Map.get(Librarian.get_book_names(book: bname), :short, nil)

    %__MODULE__{
      book: bname,
      book_names: book_names,
      book_number: Librarian.find_book_number(book: bname),
      reference:
        Librarian.create_reference_string(
          book: bname,
          start_chapter: start_chapter,
          start_verse: start_verse,
          end_chapter: ecn,
          end_verse: end_verse
        ),
      reference_type:
        Librarian.identify_reference_type(
          book: bname,
          start_chapter: scn,
          start_verse: start_verse,
          end_chapter: ecn,
          end_verse: end_verse
        ),
      start_chapter: sc,
      start_chapter_number: scn,
      end_chapter: ec,
      end_chapter_number: ecn,
      start_verse: sv,
      start_verse_number: svn,
      end_verse: ev,
      end_verse_number: evn,
      is_valid:
        Librarian.verify_reference(
          book: bname,
          start_chapter: scn,
          start_verse: start_verse,
          end_chapter: ecn,
          end_verse: end_verse
        ),
      chapters: chapters,
      verses: List.flatten(verse_collection),
      osis: osis_book,
      abbr: abbr_book,
      short: short_book
    }
  end

  @doc """
  Make a chapter `%Reference{}` struct.

  ## Parameters

    - *book*: a string that is one of the four possible book name formats
    - `"GEN"` (abbr)
    - `"Genesis"` (name)
    - `"Gen"` (osis)
    - `"Gn"` (short)
    - *chapter*: an integer that is the chapter number

  ## Examples
      iex> alias BibleEx.Reference
      iex> genesis_ref = Reference.chapter(book: "Genesis", chapter: 1)

      %BibleEx.Reference{
        book: "Genesis",
        book_names: %{abbr: "GEN", name: "Genesis", osis: "Gen", short: "Gn"},
        book_number: 1,
        reference: "Genesis 1",
        reference_type: :chapter,
        start_chapter: %BibleEx.Chapter{
        ...
        }
      }
  """

  def chapter(book: book, chapter: chapter) do
    new(book: book, start_chapter: chapter)
  end

  @doc """
  Make a verse `%Reference{}` struct.

  ## Parameters

    - *book*: a string that is one of the four possible book name formats
    - `"GEN"` (abbr)
    - `"Genesis"` (name)
    - `"Gen"` (osis)
    - `"Gn"` (short)
    - *chapter*: an integer that is the chapter number
    - *verse*: an integer that is the verse number

  ## Examples
      iex> alias BibleEx.Reference
      iex> gen_11 = Reference.verse(book: "Genesis", chapter: 1, verse: 1)

      %BibleEx.Reference{
        book: "Genesis",
        book_names: %{abbr: "GEN", name: "Genesis", osis: "Gen", short: "Gn"},
        book_number: 1,
        reference: "Genesis 1:1",
        reference_type: :verse,
        start_chapter: %BibleEx.Chapter{
          ...
        }
        ...
      }
  """

  def verse(book: book, chapter: chapter, verse: verse) do
    new(book: book, start_chapter: chapter, start_verse: verse)
  end

  @doc """
  Make a chapter_range `%Reference{}` struct.

  ## Parameters

    - *book*: a string that is one of the four possible book name formats
    - `"GEN"` (abbr)
    - `"Genesis"` (name)
    - `"Gen"` (osis)
    - `"Gn"` (short)
    - *start_chapter*: an integer that is the chapter number
    - *end_chapter*: an integer that is the chapter number

  ## Examples
      iex> alias BibleEx.Reference
      iex> gen_1_to_2 = Reference.verse(book: "Genesis", start_chapter: 1, end_chapter: 2)

      %BibleEx.Reference{
        book: "Genesis",
        book_names: %{abbr: "GEN", name: "Genesis", osis: "Gen", short: "Gn"},
        book_number: 1,
        reference: "Genesis 1-2",
        reference_type: :chapter_range,
        start_chapter: %BibleEx.Chapter{
          ...
        }
        ...
      }
  """

  def chapter_range(book: book, start_chapter: start_chapter, end_chapter: end_chapter) do
    new(
      book: book,
      start_chapter: start_chapter,
      start_verse: nil,
      end_chapter: end_chapter,
      end_verse: nil
    )
  end

  @doc """
  Make a verse_range `%Reference{}` struct.

  ## Parameters

    - *book*: a string that is one of the four possible book name formats
    - `"GEN"` (abbr)
    - `"Genesis"` (name)
    - `"Gen"` (osis)
    - `"Gn"` (short)
    - *start_verse*: an integer that is the start verse number
    - *end_verse*: an integer that is the end verse number

  ## Examples
      iex> alias BibleEx.Reference
      iex> matt2410 = Reference.verse_range(book: "Mat", chapter: 2, start_verse: 4, end_verse: 10)

      %BibleEx.Reference{
        book: "Matthew",
        book_names: %{abbr: "MAT", name: "Matthew", osis: "Matt", short: "Mt"},
        book_number: 40,
        reference: "Matthew 2:4-10",
        reference_type: :verse_range,
        start_chapter: %BibleEx.Chapter{
          ...
        }
        ...
      }
  """

  def verse_range(book: book, chapter: chapter, start_verse: start_verse, end_verse: end_verse) do
    new(
      book: book,
      start_chapter: chapter,
      start_verse: start_verse,
      end_chapter: nil,
      end_verse: end_verse
    )
  end
end