package main

import (
	"archive/zip"
	"bufio"
	"bytes"
	"container/list"
	"encoding/xml"
	"errors"
	"fmt"
	"github.com/beevik/etree"
	"io"
	"kavita-helper-go/jnc"
	"os"
	"os/exec"
	"path/filepath"
	"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
}

// 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 GetArg(argKey string) (string, error) {
	if slices.Contains(os.Args, argKey) {
		idx := slices.Index(os.Args, argKey)
		return os.Args[idx+1], nil
	}
	return "", errors.New("arg " + argKey + " not found")
}

// Arguments:
// -I: Interactive Mode, prompts user for required info when needed
// -D: Domain. Only required if -P jnc is set
// -L: Language Suffix for Series Title
// -S: Series Name. optional when using -I
// -P: Provider
// -d: download Directory or File Directory. Depends on set -P
// -u: username. Only required if -P jnc is set
// -p: password. Only required if -P jnc is set
// -f: file. Only required when no -P is set. Specifies the file to be used
// -n: number. Only required when no -P is set. Specifies the volume number
func main() {
	interactive := false
	if slices.Contains(os.Args, "-I") {
		interactive = true
	}

	provider, _ := GetArg("-P")

	serLan, _ := GetArg("-L")
	if serLan != "" {
		serLan = " " + serLan
	}

	switch provider {
	case "":
		{
			// figure out settings needed for series input
			series, err := GetArg("-S")
			if err != nil {
				series = GetUserInput("Enter Series Name: ")
			}

			epubData := EpubInfo{
				series: series,
			}

			dir, err := GetArg("-d")
			if err != nil {
				epubData.path, err = GetArg("-f")
				if err != nil {
					epubData.path = GetUserInput("Enter File Path: ")
					epubData.title = filepath.Base(epubData.path)[0 : len(filepath.Base(epubData.path))-len(filepath.Ext(epubData.path))]

					number, err := GetArg("-n")
					if err != nil {
						number = GetUserInput("Enter Volume Number: ")
					}
					epubData.number = number

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

					epubData.format = DetermineEpubFormat(epub)
					if epubData.format.fType == "manga" {
						panic("Manga without provider not yet handled")
					}
					ProcessEpub(epub, epubData)
				}
			} else {
				if strings.LastIndex(dir, "/") != len(dir)-1 {
					dir = dir + "/"
				}
				files, err := os.ReadDir(dir)
				if err != nil {
					panic(err)
				}
				for fIdx := range files {
					epubData.path = dir + files[fIdx].Name()
					epubData.title = files[fIdx].Name()

					epubData.number = GetUserInput(fmt.Sprintf("Enter Volume Number for '%s':", files[fIdx].Name()))

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

					epubData.format = DetermineEpubFormat(epub)
					if epubData.format.fType == "manga" {
						panic("Manga without provider not yet handled")
					}
					ProcessEpub(epub, epubData)
				}
			}
		}
	case "jnc":
		{
			if !interactive {
				panic("automatic mode for JNC is not implemented yet")
			}
			settings := jnc.ProviderSettings{
				Language: serLan,
			}

			domain, err := GetArg("-D")
			if err != nil {
				panic(err)
			}
			settings.Domain = domain

			downloadDir, err := GetArg("-d")
			if err != nil {
				panic(err)
			}
			if strings.LastIndex(downloadDir, "/") != len(downloadDir)-1 {
				downloadDir = downloadDir + "/"
			}
			settings.Directory = downloadDir

			series, err := GetArg("-S")
			if err == nil {
				settings.Series = series
			}

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

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

			RunJNCProvider(settings)
		}
	}

}

func RunJNCProvider(settings jnc.ProviderSettings) {
	// initialize the J-Novel Club Instance
	jnovel := jnc.NewJNC(settings.Domain)
	jnovel.SetUsername(settings.Username)
	jnovel.SetPassword(settings.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, settings.Directory, updatedOnly == "Y" || updatedOnly == "y", settings.Language)
				}
			}
			if mode == "3" || mode == "5" {
				if serie.Info.Type == "NOVEL" {
					HandleSeries(jnovel, serie, settings.Directory, updatedOnly == "Y" || updatedOnly == "y", settings.Language)
				}
			}
		}
		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, settings.Directory, false, settings.Language)
		} 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, settings.Directory, settings.Language)
		}
	}
}

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

	}
}

func PrepareSerieDirectory(serie jnc.SerieAugmented, volume jnc.VolumeAugmented, downloadDir string, titleSuffix 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+titleSuffix {
		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 + titleSuffix
			} else {
				splits[len(splits)-1] = serie.Info.Title + part + titleSuffix
				splits = append(splits, "")
			}
			downloadDir = strings.Join(splits, "/")
		} else {
			downloadDir += serie.Info.Title + titleSuffix + "/"
		}

		_, 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, titleSuffix 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
	}

	file, err := jnovel.Download(downloadLink, downloadDir)
	if err != nil {
		panic(err)
	}
	volume.Info, err = jnovel.FetchVolumeInfo(volume.Info)
	if err != nil {
		fmt.Printf("Error fetching volume info: %s\n", err)
	}

	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] + titleSuffix
		number = splits[3]
	} else {
		title = serie.Info.Title + titleSuffix
		number = strconv.Itoa(volume.Info.Number)
	}

	epubData := EpubInfo{
		path:   file,
		title:  volume.Info.Title,
		series: title,
		number: number,
		info: ComicInfo{
			Publisher:  "J-Novel Club",
			Tags:       strings.Join(serie.Info.Tags, ","),
			Series:     serie.Info.Title,
			Volume:     volume.Info.Number,
			Summary:    volume.Info.Description,
			Writer:     volume.Info.Creators.TryGetName("AUTHOR"),
			Translator: volume.Info.Creators.TryGetName("TRANSLATOR"),
			Editor:     volume.Info.Creators.TryGetName("EDITOR"),
		},
	}

	if volume.Info.Creators.Contains("ARTIST") {
		epubData.info.CoverArtist = volume.Info.Creators.Get("ARTIST").Name
	} else if volume.Info.Creators.Contains("ILLUSTRATOR") {
		epubData.info.CoverArtist = volume.Info.Creators.Get("ILLUSTRATOR").Name
	}

	if volume.Info.Creators.Contains("LETTERER") {
		epubData.info.Letterer = volume.Info.Creators.Get("LETTERER").Name
	} else if volume.Info.Creators.Contains("ILLUSTRATOR") {
		epubData.info.Letterer = volume.Info.Creators.Get("ILLUSTRATOR").Name
	}

	publishingTime, err := time.Parse(time.RFC3339, volume.Info.Publishing)
	if err != nil {
		panic(err)
	}
	epubData.info.Year = publishingTime.Year()
	epubData.info.Month = int(publishingTime.Month())
	epubData.info.Day = publishingTime.Day()

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

	epubData.format = DetermineEpubFormat(epub)
	ProcessEpub(epub, epubData)
}

type EpubInfo struct {
	path   string
	title  string
	series string
	number string
	info   ComicInfo
	format FormatInfo
}

type FormatInfo struct {
	fType      string
	metadata   zip.File
	navigation zip.File
}

func DetermineEpubFormat(epub *zip.ReadCloser) FormatInfo {
	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"

	metaInfFileIdx := slices.IndexFunc(epub.Reader.File, func(f *zip.File) bool {
		if f.Name == MetaInf {
			return true
		}
		return false
	})

	metaInfFile := epub.Reader.File[metaInfFileIdx]
	doc := etree.NewDocument()
	reader, err := metaInfFile.Open()
	if err != nil {
		fmt.Println(err)
	}

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

	metadataFileName := doc.FindElement("//container/rootfiles/rootfile").SelectAttr("full-path").Value
	metaFileIdx := slices.IndexFunc(epub.Reader.File, func(f *zip.File) bool {
		if f.Name == metadataFileName {
			return true
		}
		return false
	})
	metadata := *epub.Reader.File[metaFileIdx]

	navigationFileName := FormatNavigationFile[FormatMetadataName[metadataFileName]]
	navigationFileIdx := slices.IndexFunc(epub.Reader.File, func(f *zip.File) bool {
		if f.Name == navigationFileName {
			return true
		}
		return false
	})
	navigation := *epub.Reader.File[navigationFileIdx]

	return FormatInfo{
		fType:      FormatMetadataName[metadataFileName],
		metadata:   metadata,
		navigation: navigation,
	}
}

func ProcessEpub(epub *zip.ReadCloser, epubData EpubInfo) {
	// Switch based on metadata file
	switch epubData.format.fType {
	case "manga":
		{
			comicInfoName := "ComicInfo.xml"
			chapterList := GenerateMangaChapterList(epubData.format.navigation)

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

			basePath := filepath.Dir(epubData.path) + "/" + epubData.title
			PrepareVolumeDirectory(basePath)

			doc := etree.NewDocument()
			reader, err := epubData.format.metadata.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 + "/" + epubData.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(epubData, 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(epubData.path)
		}
	case "novel":
		{
			CalibreSeriesAttr := "calibre:series"
			CalibreSeriesIndexAttr := "calibre:series_index"
			EpubTitleTag := "//package/metadata"

			metafile, err := epubData.format.metadata.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)
			seriesTitle.CreateAttr("content", epubData.series)

			seriesNumber := etree.NewElement("meta")
			seriesNumber.CreateAttr("name", CalibreSeriesIndexAttr)
			seriesNumber.CreateAttr("content", epubData.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(epubData.path+".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(epubData.format.metadata.Name)
			if err != nil {
				panic(err)
			}

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

			for _, f := range epub.File {
				if f.Name != epubData.format.metadata.Name {
					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(epubData.path + ".new")
			dest, _ := os.Create(epubData.path)
			io.Copy(dest, source)
			os.Remove(epubData.path + ".new")
		}
	}
}

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

	comicInfo.Series = epubData.info.Series
	comicInfo.Title = epubData.title
	comicInfo.Number = chapterNumber
	comicInfo.Volume = epubData.info.Volume

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

	comicInfo.Summary = epubData.info.Summary

	comicInfo.Year = epubData.info.Year
	comicInfo.Month = epubData.info.Month
	comicInfo.Day = epubData.info.Day

	if epubData.info.Writer != "" {
		comicInfo.Writer = epubData.info.Writer
	}

	if epubData.info.Letterer != "" {
		comicInfo.Letterer = epubData.info.Letterer
	}

	if epubData.info.CoverArtist != "" {
		comicInfo.CoverArtist = epubData.info.CoverArtist
	}

	if epubData.info.Translator != "" {
		comicInfo.Translator = epubData.info.Translator
	}

	if epubData.info.Editor != "" {
		comicInfo.Editor = epubData.info.Editor
	}

	comicInfo.Publisher = epubData.info.Publisher

	comicInfo.Tags = epubData.info.Tags
	comicInfo.PageCount = pageCount
	comicInfo.Language = language
	comicInfo.Format = "Comic" // JNC reports this as type in the epub file

	return xml.MarshalIndent(comicInfo, " ", " ")
}

type ComicInfo struct {
	XMLName     string `xml:"ComicInfo"`
	XMLNS       string `xml:"xmlns:xsi,attr"`
	XSI         string `xml:"xsi:noNamespaceSchemaLocation,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
			}
		} else {
			break
		}
	}
	return currentChapter.Value.(Chapter)
}

func GenerateMangaChapterList(navigation zip.File) *list.List {
	reader, err := navigation.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-9IVXLCDM]*)"
		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
}