import std/[strutils, tables]

import ./reference_parser

type BibleIndex = object
  verses: Table[string, string]
  lastVerseByChapter: Table[string, int]
  lastChapterByBook: Table[string, int]
  translationName: string

proc verseKey(code: string, chapter, verse: int): string =
  code & "\t" & $chapter & "\t" & $verse

proc chapterKey(code: string, chapter: int): string =
  code & "\t" & $chapter

proc loadBibleIndex(rows, translationName: string): BibleIndex =
  result.translationName = translationName

  for line in rows.splitLines:
    if line.strip.len == 0:
      continue

    let parts = line.split('\t', maxsplit = 3)
    if parts.len != 4:
      raise newException(ValueError,
        "invalid embedded " & translationName & " row: " & line)

    let code = parts[0]
    let chapter = parseInt(parts[1])
    let verse = parseInt(parts[2])
    let text = parts[3]

    result.verses[verseKey(code, chapter, verse)] = text

    let cKey = chapterKey(code, chapter)
    if not result.lastVerseByChapter.hasKey(cKey) or
        verse > result.lastVerseByChapter[cKey]:
      result.lastVerseByChapter[cKey] = verse

    if not result.lastChapterByBook.hasKey(code) or
        chapter > result.lastChapterByBook[code]:
      result.lastChapterByBook[code] = chapter

proc requireLastChapter(index: BibleIndex, code: string): int =
  if not index.lastChapterByBook.hasKey(code):
    raise newException(ValueError,
      "no embedded " & index.translationName & " data for " & code)
  index.lastChapterByBook[code]

proc requireLastVerse(index: BibleIndex, code: string, chapter: int): int =
  let cKey = chapterKey(code, chapter)
  if not index.lastVerseByChapter.hasKey(cKey):
    raise newException(ValueError,
      "no embedded " & index.translationName & " data for " &
        bookInfo(code).name & " " & $chapter)
  index.lastVerseByChapter[cKey]

proc requireVerse(index: BibleIndex, code: string, chapter, verse: int): string =
  let vKey = verseKey(code, chapter, verse)
  if not index.verses.hasKey(vKey):
    raise newException(ValueError,
      "no embedded " & index.translationName & " data for " &
        bookInfo(code).name & " " & $chapter & ":" & $verse)
  index.verses[vKey]

proc addVerseLines(
    lines: var seq[string],
    index: BibleIndex,
    reference: PassageReference,
    range: RefRange) =

  let code = reference.book.code
  discard index.requireLastChapter(code)

  for chapter in range.start.chapter .. range.finish.chapter:
    let startVerse =
      if chapter == range.start.chapter and range.start.verse > 0:
        range.start.verse
      else:
        1

    let endVerse =
      if chapter == range.finish.chapter and range.finish.verse > 0:
        range.finish.verse
      else:
        index.requireLastVerse(code, chapter)

    if startVerse > endVerse:
      raise newException(ValueError, "reference range starts after it ends")

    for verse in startVerse .. endVerse:
      lines.add("  [" & $verse & "] " & index.requireVerse(code, chapter, verse))

proc fetchReference(index: BibleIndex, reference: PassageReference): string =
  var lines = @[$reference]
  let code = reference.book.code

  if reference.ranges.len == 0:
    for chapter in 1 .. index.requireLastChapter(code):
      for verse in 1 .. index.requireLastVerse(code, chapter):
        lines.add("  [" & $verse & "] " & index.requireVerse(code, chapter, verse))
  else:
    for range in reference.ranges:
      lines.addVerseLines(index, reference, range)

  lines.join("\n")

proc fetchPassages*(rows, reference, translationName: string): seq[string] =
  let index = loadBibleIndex(rows, translationName)
  for parsedReference in parseReferences(reference):
    result.add(fetchReference(index, parsedReference))
