package main

import (
	"archive/zip"
	"bufio"
	"bytes"
	"container/list"
	"encoding/xml"
	"fmt"
	"github.com/beevik/etree"
	"io"
	"kavita-helper-go/jnc"
	"os"
	"os/exec"
	"regexp"
	"slices"
	"strconv"
	"strings"
	"time"
)

type Chapter struct {
	numberMain int8
	numberSub  int8 // uses 0 for regular main chapters
	chDisplay  string
	title      string
	firstPage  string
	lastPage   string
	pages      []string
}

type FileWrapper struct {
	fileName string
	file     zip.File
	fileSet  bool
}

func NewFileWrapper() FileWrapper {
	newFileWrapper := FileWrapper{}
	newFileWrapper.fileSet = false
	newFileWrapper.fileName = ""
	return newFileWrapper
}

// TODO's
// [ ] Create Chapter .cbz's based on given Information (no ComicInfo.xml yet)
// 		[ ] Sort Into Volume Folders (requires following step)
// [ ] Get ComicInfo.xml data from the J-Novel Club API
// [ ] Get the Manga from the J-Novel Club API

func GetUserInput(prompt string) string {
	reader := bufio.NewScanner(os.Stdin)
	fmt.Print(prompt)
	reader.Scan()
	err := reader.Err()
	if err != nil {
		panic(err)
	}
	return reader.Text()
}

func ClearScreen() {
	cmd := exec.Command("clear")
	cmd.Stdout = os.Stdout
	if err := cmd.Run(); err != nil {
		fmt.Println("[ERROR] - ", err)
	}
}

func main() {
	interactive := false
	if slices.Contains(os.Args, "-I") {
		interactive = true
	}

	if !interactive {
		panic("automatic mode is not implemented yet")
	}

	var downloadDir string
	if slices.Contains(os.Args, "-d") {
		idx := slices.Index(os.Args, "-d")
		downloadDir = os.Args[idx+1]
		if strings.LastIndex(downloadDir, "/") != len(downloadDir)-1 {
			downloadDir = downloadDir + "/"
		}
	} else {
		panic("working directory not specified")
	}

	// initialize the J-Novel Club Instance
	jnovel := jnc.NewJNC()

	var username string
	if slices.Contains(os.Args, "-u") {
		idx := slices.Index(os.Args, "-u")
		username = os.Args[idx+1]
	} else {
		username = GetUserInput("Enter Username: ")
	}
	jnovel.SetUsername(username)

	var password string
	if slices.Contains(os.Args, "-p") {
		idx := slices.Index(os.Args, "-p")
		password = os.Args[idx+1]
	} else {
		password = GetUserInput("Enter Password: ")
	}
	jnovel.SetPassword(password)

	err := jnovel.Login()
	if err != nil {
		panic(err)
	}
	defer func(jnovel *jnc.Api) {
		err := jnovel.Logout()
		if err != nil {
			panic(err)
		}
	}(&jnovel)

	err = jnovel.FetchLibrary()
	if err != nil {
		panic(err)
	}

	err = jnovel.FetchLibrarySeries()
	if err != nil {
		panic(err)
	}

	seriesList, err := jnovel.GetLibrarySeries()
	if err != nil {
		panic(err)
	}

	ClearScreen()

	fmt.Println("[1] - Download Single Volume")
	fmt.Println("[2] - Download Entire Series")
	fmt.Println("[3] - Download Entire Library")
	fmt.Println("[4] - Download Entire Library [MANGA only]")
	fmt.Println("[5] - Download Entire Library [NOVEL only]")
	mode := GetUserInput("Select Mode: ")

	ClearScreen()

	if mode == "3" || mode == "4" || mode == "5" {
		updatedOnly := GetUserInput("Download Only Where Newer Files Are Available? [Y/N]: ")

		for s := range seriesList {
			serie := seriesList[s]
			if mode == "3" || mode == "4" {
				if serie.Info.Type == "MANGA" {
					HandleSeries(jnovel, serie, downloadDir, updatedOnly == "Y" || updatedOnly == "y")
				}
			}
			if mode == "3" || mode == "5" {
				if serie.Info.Type == "NOVEL" {
					HandleSeries(jnovel, serie, downloadDir, updatedOnly == "Y" || updatedOnly == "y")
				}
			}
		}
		fmt.Println("Done")
	} else {
		fmt.Println("\n###[Series Selection]###")
		for s := range seriesList {
			serie := seriesList[s].Info

			fmt.Printf("[%s] - [%s] - %s\n", strconv.Itoa(s+1), serie.Type, serie.Title)
		}

		msg := "Enter Series Number: "
		var seriesNumber int
		for {
			selection := GetUserInput(msg)
			seriesNumber, err = strconv.Atoi(selection)
			if err != nil {
				msg = "\rInvalid. Enter VALID Series Number: "
			}
			break
		}

		serie := seriesList[seriesNumber-1]

		if mode == "2" {
			HandleSeries(jnovel, serie, downloadDir, false)
		} else {
			ClearScreen()
			fmt.Println("\n###[Volume Selection]###")
			for i := range serie.Volumes {
				vol := serie.Volumes[i]
				fmt.Printf("[%s] - %s\n", strconv.Itoa(i+1), vol.Info.Title)
			}

			msg = "Enter Volume Number: "
			var volumeNumber int
			for {
				selection := GetUserInput(msg)
				volumeNumber, err = strconv.Atoi(selection)
				if err != nil {
					msg = "\rInvalid. Enter VALID Volume Number: "
				}
				break
			}

			volume := serie.Volumes[volumeNumber-1]
			HandleVolume(jnovel, serie, volume, downloadDir)
		}
	}
}

func HandleSeries(jnovel jnc.Api, serie jnc.SerieAugmented, downloadDir string, updatedOnly bool) {
	for v := range serie.Volumes {
		volume := serie.Volumes[v]
		if updatedOnly {
			if len(volume.Downloads) != 0 && volume.UpdateAvailable() {
				downloadDir = PrepareSerieDirectory(serie, volume, downloadDir)
				HandleVolume(jnovel, serie, volume, downloadDir)
			}
		} else {
			downloadDir = PrepareSerieDirectory(serie, volume, downloadDir)
			HandleVolume(jnovel, serie, volume, downloadDir)
		}

	}
}

func PrepareSerieDirectory(serie jnc.SerieAugmented, volume jnc.VolumeAugmented, downloadDir string) string {
	splits := strings.Split(downloadDir, "/")
	// last element of this split is always empty due to the trailing slash
	if splits[len(splits)-2] != serie.Info.Title {
		if serie.Info.Title == "Ascendance of a Bookworm" {
			partSplits := strings.Split(volume.Info.ShortTitle, " ")
			part := " " + partSplits[0] + " " + partSplits[1]

			if strings.Split(splits[len(splits)-2], " ")[0] == "Ascendance" {
				splits[len(splits)-2] = serie.Info.Title + part
			} else {
				splits[len(splits)-1] = serie.Info.Title + part
				splits = append(splits, "")
			}
			downloadDir = strings.Join(splits, "/")
		} else {
			downloadDir += serie.Info.Title + "/"
		}

		_, err := os.Stat(downloadDir)
		if err != nil {
			err = os.Mkdir(downloadDir, 0755)
			if err != nil {
				panic(err)
			}
		}
	}

	return downloadDir
}

func HandleVolume(jnovel jnc.Api, serie jnc.SerieAugmented, volume jnc.VolumeAugmented, downloadDir string) {
	var downloadLink string
	if len(volume.Downloads) == 0 {
		fmt.Printf("Volume %s currently has no downloads available. Skipping \n", volume.Info.Title)
		return
	} else if len(volume.Downloads) > 1 {
		fmt.Println("TODO: implement download selection/default select highest quality")
		downloadLink = volume.Downloads[len(volume.Downloads)-1].Link
	} else {
		downloadLink = volume.Downloads[0].Link
	}

	DownloadAndProcessEpub(jnovel, serie, volume, downloadLink, downloadDir)
}

func DownloadAndProcessEpub(jnovel jnc.Api, serie jnc.SerieAugmented, volume jnc.VolumeAugmented, downloadLink string, downloadDirectory string) {
	FormatNavigationFile := map[string]string{
		"manga": "item/nav.ncx",
		"novel": "OEBPS/toc.ncx",
	}

	FormatMetadataName := map[string]string{
		"item/comic.opf":    "manga",
		"OEBPS/content.opf": "novel",
	}

	MetaInf := "META-INF/container.xml"
	file, err := jnovel.Download(downloadLink, downloadDirectory)
	if err != nil {
		panic(err)
	}

	epub, err := zip.OpenReader(file)
	if err != nil {
		fmt.Println(err)
	}

	metadata := NewFileWrapper()
	navigation := NewFileWrapper()

	for i := range epub.Reader.File {
		file := epub.Reader.File[i]

		if file.Name == MetaInf {
			doc := etree.NewDocument()
			reader, err := file.Open()
			if err != nil {
				fmt.Println(err)
			}

			if _, err := doc.ReadFrom(reader); err != nil {
				_ = reader.Close()
				panic(err)
			}

			metadata.fileName = doc.FindElement("//container/rootfiles/rootfile").SelectAttr("full-path").Value
			navigation.fileName = FormatNavigationFile[FormatMetadataName[metadata.fileName]]
		}

		if len(metadata.fileName) != 0 && file.Name == metadata.fileName {
			metadata.file = *file
			metadata.fileSet = true
		}

		if len(navigation.fileName) != 0 && file.Name == navigation.fileName {
			navigation.file = *file
			navigation.fileSet = true
		}

		if metadata.fileSet && navigation.fileSet {
			break
		}
	}

	// Switch based on metadata file

	switch FormatMetadataName[metadata.fileName] {
	case "manga":
		{
			comicInfoName := "ComicInfo.xml"
			chapterList := GenerateMangaChapterList(navigation)

			if chapterList.Len() == 0 {
				fmt.Println("No chapters found, chapter name likely not supported")
			}

			basePath := downloadDirectory + volume.Info.Title + "/"
			PrepareVolumeDirectory(basePath)
			volume.Info, err = jnovel.FetchVolumeInfo(volume.Info)
			if err != nil {
				fmt.Println(err)
			}

			doc := etree.NewDocument()
			reader, err := metadata.file.Open()
			if err != nil {
				fmt.Println(err)
			}

			if _, err := doc.ReadFrom(reader); err != nil {
				_ = reader.Close()
				panic(err)
			}

			language := doc.FindElement("package/metadata/dc:language").Text()

			for ch := chapterList.Front(); ch != nil; ch = ch.Next() {
				chap := ch.Value.(Chapter)

				zipPath := basePath + volume.Info.Title + " Chapter " + chap.chDisplay + ".cbz"

				if _, err = os.Stat(zipPath); err != nil {
					err := os.Remove(zipPath)
					if err != nil {
						return
					}
				}
				newZipFile, err := os.Create(zipPath)
				if err != nil {
					panic(err)
				}

				newZip := zip.NewWriter(newZipFile)

				for chapterPageIndex := range chap.pages {
					chapterFile := chap.pages[chapterPageIndex]

					epubFileIndex := slices.IndexFunc(epub.File, func(f *zip.File) bool {
						fParts := strings.Split(f.Name, "/")
						cParts := strings.Split(chapterFile, "/")
						return fParts[len(fParts)-1] == cParts[len(cParts)-1]
					})

					epubFile := epub.File[epubFileIndex]

					doc := etree.NewDocument()
					reader, err := epubFile.Open()
					if err != nil {
						fmt.Println(err)
					}

					if _, err := doc.ReadFrom(reader); err != nil {
						_ = reader.Close()
						panic(err)
					}

					imageFilePath := doc.FindElement("//html/body/svg/image").SelectAttr("href").Value
					_ = reader.Close()

					if imageFilePath[2:] == ".." {
						fParts := strings.Split(epubFile.Name, "/")
						imageFilePath = fParts[len(fParts)-2] + imageFilePath[2:len(imageFilePath)-1]
					}

					iParts := strings.Split(imageFilePath, "/")
					imageFileIndex := slices.IndexFunc(epub.File, func(f *zip.File) bool {
						fParts := strings.Split(f.Name, "/")
						return fParts[len(fParts)-1] == iParts[len(iParts)-1]
					})

					imageFile := epub.File[imageFileIndex]
					fileName := fmt.Sprintf("%02d", chapterPageIndex+1) + "." + strings.Split(iParts[len(iParts)-1], ".")[1]

					err = addFileToZip(newZip, imageFile, fileName)
					if err != nil {
						fmt.Println(err)
					}
				}

				comicInfo, err := GenerateChapterMetadata(volume, serie, len(chap.pages), language, chap.chDisplay)
				if err != nil {
					fmt.Println(err)
				}

				err = addBytesToZip(newZip, comicInfo, comicInfoName)
				if err != nil {
					fmt.Println(err)
				}

				newZip.Close()
				newZipFile.Close()
			}

			epub.Close()
			os.Remove(file)
		}
	case "novel":
		{
			CalibreSeriesAttr := "calibre:series"
			CalibreSeriesIndexAttr := "calibre:series_index"
			EpubTitleTag := "//package/metadata"

			metafile, err := metadata.file.Open()
			if err != nil {
				fmt.Println(err)
			}

			doc := etree.NewDocument()
			if _, err = doc.ReadFrom(metafile); err != nil {
				fmt.Println(err)
			}

			idx := doc.FindElement(EpubTitleTag + "/dc:title").Index()

			seriesTitle := etree.NewElement("meta")
			seriesTitle.CreateAttr("name", CalibreSeriesAttr)
			var title string
			var number string
			if serie.Info.Title == "Ascendance of a Bookworm" {
				splits := strings.Split(volume.Info.ShortTitle, " ")
				title = serie.Info.Title + " " + splits[0] + " " + splits[1]
				number = splits[3]
			} else {
				title = serie.Info.Title
				number = strconv.Itoa(volume.Info.Number)
			}

			seriesTitle.CreateAttr("content", title)

			seriesNumber := etree.NewElement("meta")
			seriesNumber.CreateAttr("name", CalibreSeriesIndexAttr)
			seriesNumber.CreateAttr("content", number)

			doc.FindElement(EpubTitleTag).InsertChildAt(idx+1, seriesTitle)
			doc.FindElement(EpubTitleTag).InsertChildAt(idx+2, seriesNumber)
			doc.Indent(2)

			// Open the zip file for writing
			zipfile, err := os.OpenFile(file+".new", os.O_RDWR|os.O_CREATE, 0644)
			if err != nil {
				panic(err)
			}
			defer func(zipfile *os.File) {
				err := zipfile.Close()
				if err != nil {
					panic(err)
				}
			}(zipfile)

			zipWriter := zip.NewWriter(zipfile)

			str, err := doc.WriteToString()
			if err != nil {
				panic(err)
			}

			metawriter, err := zipWriter.Create(metadata.fileName)
			if err != nil {
				panic(err)
			}

			_, err = metawriter.Write([]byte(str))
			if err != nil {
				panic(err)
			}

			for _, f := range epub.File {
				if f.Name != metadata.fileName {
					writer, err := zipWriter.Create(f.Name)
					if err != nil {
						panic(err)
					}
					reader, err := f.Open()
					if err != nil {
						panic(err)
					}

					_, err = io.Copy(writer, reader)
					if err != nil {
						_ = reader.Close()
						panic(err)
					}
				}
			}

			epub.Close()
			zipWriter.Close()
			source, _ := os.Open(file + ".new")
			dest, _ := os.Create(file)
			io.Copy(dest, source)
			os.Remove(file + ".new")
		}
	}
}

func GenerateChapterMetadata(volume jnc.VolumeAugmented, serie jnc.SerieAugmented, pageCount int, language string, chapterNumber string) ([]byte, error) {
	comicInfo := ComicInfo{
		XMLName: "ComicInfo",
		XMLNS:   "http://www.w3.org/2001/XMLSchema-instance",
		XSI:     "ComicInfo.xsd",
	}

	vInfo := volume.Info
	sInfo := serie.Info

	comicInfo.Series = sInfo.Title
	comicInfo.Title = vInfo.Title
	comicInfo.Number = chapterNumber
	comicInfo.Volume = vInfo.Number

	comicInfo.Count = -1 // TODO somehow fetch actual completion status

	comicInfo.Summary = vInfo.Description

	publishingTime, err := time.Parse(time.RFC3339, vInfo.Publishing)
	if err != nil {
		return nil, err
	}
	comicInfo.Year = publishingTime.Year()
	comicInfo.Month = int(publishingTime.Month())
	comicInfo.Day = publishingTime.Day()

	if vInfo.Creators.Contains("AUTHOR") {
		comicInfo.Writer = vInfo.Creators.Get("AUTHOR").Name
	}

	if vInfo.Creators.Contains("ILLUSTRATOR") {
		comicInfo.Letterer = vInfo.Creators.Get("ILLUSTRATOR").Name
		comicInfo.CoverArtist = vInfo.Creators.Get("ILLUSTRATOR").Name
	}

	if vInfo.Creators.Contains("TRANSLATOR") {
		comicInfo.Translator = vInfo.Creators.Get("TRANSLATOR").Name
	}

	if vInfo.Creators.Contains("EDITOR") {
		comicInfo.Editor = vInfo.Creators.Get("EDITOR").Name
	}

	if vInfo.Creators.Contains("PUBLISHER") {
		comicInfo.Publisher = vInfo.Creators.Get("PUBLISHER").Name
	}

	comicInfo.Tags = strings.Join(sInfo.Tags, ",")
	comicInfo.PageCount = pageCount
	comicInfo.Language = language
	comicInfo.Format = "Comic" // JNC reports this as type in the epub file

	return xml.Marshal(comicInfo)
}

type ComicInfo struct {
	XMLName     string `xml:"ComicInfo"`
	XMLNS       string `xml:"xmlns,attr"`
	XSI         string `xml:"xsi,attr"`
	Series      string `xml:"Series"`
	Title       string `xml:"Title"`
	Volume      int    `xml:"Volume"`
	Number      string `xml:"Number"`
	Count       int    `xml:"Count"`
	Summary     string `xml:"Summary"`
	Year        int    `xml:"Year"`
	Month       int    `xml:"Month"`
	Day         int    `xml:"Day"`
	Writer      string `xml:"Writer"`
	Letterer    string `xml:"Letterer"`
	CoverArtist string `xml:"CoverArtist"`
	Editor      string `xml:"Editor"`
	Translator  string `xml:"Translator"`
	Publisher   string `xml:"Publisher"`
	Tags        string `xml:"Tags"`
	PageCount   int    `xml:"PageCount"`
	Language    string `xml:"LanguageISO"`
	Format      string `xml:"Format"`
}

func addBytesToZip(zipWriter *zip.Writer, fileBytes []byte, filename string) error {
	reader := bytes.NewReader(fileBytes)

	w, err := zipWriter.Create(filename)
	if err != nil {
		return error(err)
	}

	if _, err := io.Copy(w, reader); err != nil {
		return error(err)
	}

	return nil
}

func addFileToZip(zipWriter *zip.Writer, file *zip.File, filename string) error {
	fileToZip, err := file.Open()
	if err != nil {
		return error(err)
	}
	defer fileToZip.Close()

	w, err := zipWriter.Create(filename)
	if err != nil {
		return error(err)
	}

	if _, err := io.Copy(w, fileToZip); err != nil {
		return error(err)
	}

	return nil
}

func PrepareVolumeDirectory(directory string) {
	_, err := os.Stat(directory)
	if err != nil {
		err = os.Mkdir(directory, 0755)
		if err != nil {
			panic(err)
		}
	}
}

func FinalizeChapters(chapters *list.List, pageList []*etree.Element) *list.List {
	fmt.Println("Finalizing Chapters")

	sortedChapters := list.New()

	var mainChapter Chapter

	for ch := chapters.Back(); ch != nil; ch = ch.Prev() {
		currentChapter := ch.Value.(Chapter)

		if currentChapter.numberMain != -1 {
			// pages
			if currentChapter.numberSub != 0 {
				numberMain, numberSub := AnalyzeMainChapter(ch)
				currentChapter.numberMain = numberMain
				currentChapter.numberSub = numberSub
				currentChapter.chDisplay = strconv.FormatInt(int64(currentChapter.numberMain), 10) + "." + strconv.FormatInt(int64(currentChapter.numberSub), 10)
			} else {
				currentChapter.chDisplay = strconv.FormatInt(int64(currentChapter.numberMain), 10)
			}

			currentChapter.lastPage = pageList[len(pageList)-1].SelectAttr("src").Value
			collecting := false
			for p := range pageList {
				page := pageList[p].SelectAttr("src").Value
				if page == currentChapter.firstPage {
					collecting = true
					currentChapter.pages = append(currentChapter.pages, page)
				} else if ch.Next() != nil && page == ch.Next().Value.(Chapter).firstPage {
					currentChapter.lastPage = pageList[p-1].SelectAttr("src").Value
					collecting = false
				} else if collecting {
					currentChapter.pages = append(currentChapter.pages, page)
				}
			}

			sortedChapters.PushFront(currentChapter)
		} else {
			mainChapter = GetLastValidChapterNumber(ch)

			for sch := sortedChapters.Front(); sch != nil; sch = sch.Next() {
				if sch.Value.(Chapter).title == mainChapter.title {
					mainChapterData := sch.Value.(Chapter)
					mainChapterData.firstPage = currentChapter.firstPage

					collecting := false
					pages := []string{}
					for p := range pageList {
						page := pageList[p].SelectAttr("src").Value
						if page == currentChapter.firstPage {
							collecting = true
							pages = append(pages, page)
						} else if ch.Next() != nil && page == ch.Next().Value.(Chapter).firstPage {
							currentChapter.lastPage = pageList[p-1].SelectAttr("src").Value
							collecting = false
						} else if collecting {
							pages = append(pages, page)
						}
					}
					mainChapterData.pages = append(pages, mainChapterData.pages...)
					sch.Value = mainChapterData
					break
				}
			}
		}
	}
	return sortedChapters
}

func AnalyzeMainChapter(currentChapter *list.Element) (int8, int8) {
	var currentChapterNumber int8
	subChapterCount := 1

	if currentChapter.Next() != nil {
		ch := currentChapter.Next()
		for {
			if ch.Value.(Chapter).numberSub != 0 {
				subChapterCount++
				if ch.Next() != nil {
					ch = ch.Next()
				} else {
					break
				}
			} else {
				break
			}
		}
	}

	if currentChapter.Prev() != nil {
		ch := currentChapter.Prev()
		// Then Get the Current Main Chapter
		for {
			if ch.Value.(Chapter).numberSub != 0 {
				subChapterCount++
				if ch.Prev() != nil {
					ch = ch.Prev()
				} else {
					break
				}
			} else {
				subChapterCount++ // actual Chapter also counts in this case
				currentChapterNumber = ch.Value.(Chapter).numberMain
				break
			}
		}
	}

	// Calculate integer sub number based on total subChapterC
	subChapterNumber := int8((float32(currentChapter.Value.(Chapter).numberSub) / float32(subChapterCount)) * 10)

	// return main Chapter Number + CurrentChapter Sub Number
	return currentChapterNumber, subChapterNumber
}

func GetLastValidChapterNumber(currentChapter *list.Element) Chapter {
	for {
		if currentChapter.Next() != nil {
			currentChapter = currentChapter.Next()
			chapterData := currentChapter.Value.(Chapter)
			if chapterData.numberMain != -1 && chapterData.numberSub == 0 {
				return chapterData
			}
		}
		break
	}
	return currentChapter.Value.(Chapter)
}

func GenerateMangaChapterList(navigation FileWrapper) *list.List {
	reader, err := navigation.file.Open()
	if err != nil {
		fmt.Println(err)
	}

	doc := etree.NewDocument()
	if _, err = doc.ReadFrom(reader); err != nil {
		fmt.Println(err)
	}

	navMap := doc.FindElement("//ncx/navMap")
	navChapters := navMap.FindElements("//navPoint")
	chapters := list.New()

	pageList := doc.FindElements("//ncx/pageList/pageTarget/content")

	lastMainChapter := int8(-1)
	subChapter := int8(0)

	for c := range len(navChapters) {
		navChapter := navChapters[c]
		label := navChapter.FindElement("./navLabel/text").Text()
		page := navChapter.FindElement("./content")

		regex := "(?:Chapter|Episode) ([0-9]*)"
		match, _ := regexp.MatchString(regex, label)

		if match {
			r, _ := regexp.Compile(regex)
			num := r.FindStringSubmatch(label)[1]
			parse, _ := strconv.ParseInt(num, 10, 8)
			if int8(parse) == 0 {
				fmt.Printf("Unlikely Chapter Number Detected (0): %s/n", num)
				fmt.Println("Attempting Roman Numerals")
				lastMainChapter = int8(romanToInt(num))
			} else {
				lastMainChapter = int8(parse)
			}
			subChapter = int8(0)

		} else {
			if lastMainChapter == -1 {
				subChapter -= 1
			} else {
				subChapter += 1
			}
		}

		chapterData := Chapter{
			numberMain: lastMainChapter,
			numberSub:  subChapter,
			chDisplay:  "",
			title:      label,
			firstPage:  page.SelectAttr("src").Value,
			lastPage:   "",
			pages:      []string{},
		}

		chapters.PushBack(chapterData)
	}

	return FinalizeChapters(chapters, pageList)
}

func romanToInt(s string) int {

	know := map[string]int{
		"I": 1,
		"V": 5,
		"X": 10,
		"L": 50,
		"C": 100,
		"D": 500,
		"M": 1000,
	}
	lengthOfString := len(s)
	lastElement := s[len(s)-1 : lengthOfString]
	var result int
	result = know[lastElement]
	for i := len(s) - 1; i > 0; i-- {
		if know[s[i:i+1]] <= know[s[i-1:i]] {
			result += know[s[i-1:i]]
		} else {
			result -= know[s[i-1:i]]
		}
	}
	return result
}