local function DeepCopy(originalTable)
    local copyTable
    if type(originalTable) == 'table' then
        copyTable = {}
        for key, value in pairs(originalTable) do
            copyTable[key] = DeepCopy(value)
        end
    else
        copyTable = originalTable
    end
    return copyTable
end

local function GetNextBookIndex(bookIndexes)
    if bookIndexes.bookIndex >= bookIndexes.totalBooks then
        if bookIndexes.collectionIndex >= bookIndexes.numCollections then
            if bookIndexes.categoryIndex >= GetNumLoreCategories() then
                return false
            else
                bookIndexes.categoryIndex = bookIndexes.categoryIndex + 1
                bookIndexes.numCollections = select(2, GetLoreCategoryInfo(bookIndexes.categoryIndex))
                bookIndexes.collectionIndex = 0
            end
        end
        bookIndexes.collectionIndex = bookIndexes.collectionIndex + 1
        bookIndexes.totalBooks = select(4, GetLoreCollectionInfo(bookIndexes.categoryIndex, bookIndexes.collectionIndex))
        bookIndexes.bookIndex = 0
    end
    bookIndexes.bookIndex = bookIndexes.bookIndex + 1
    return true
end

function Librarian:GetBookIdWithTitle(title)
    -- list of book that aren't part of the eidetic memory
    if title == "Adventurers Wanted!" then
        return 4552
    elseif title == "Behold Khunzar-ri's Ambition" then
        return 5925
    elseif title == "Behold Khunzar-ri's Betrayal" then
        return 5673
    elseif title == "Behold Khunzar-ri's Guile" then
        return 5724
    elseif title == "Behold the Lunar Champion" then
        return 5669
    elseif title == "Beldorr's Note" then
        return 2933
    elseif title == "Bounty: Dragons!" then
        return 5708
    elseif title == "Cadwell's Personal Anthem" then
        return 2517
    elseif title == "Crumpled Note" then
        return 2925
    elseif title == "Crumpled Nursery Rhyme" then
        return 5460
    elseif title == "Details on the Midyear Mayhem" then
        return 4591
    elseif title == "Diviner's Journal" then
        return 4048
    elseif title == "Engraved Pedestal" then
        return 4006
    elseif title == "Excavation Orders" then
        return 4047
    elseif title == "Falsehoods and Fallacies of the Eight" then
        return 1279
    elseif title == "Firuth's Writ" then
        return 4042
    elseif title == "From the Exalted Viper" then -- real name is "From the Regent of Fanged Fury"
        return 2644
    elseif title == "The Ghostly Stag" then
        return 138
    elseif title == "Guardian's Decree" then
        return 4050
    elseif title == "The Indrik's Glade" then
        return 5027
    elseif title == "Journal of the King's Seneschal" then
        return 2015
    elseif title == "Journal of Ulrich" then
        return 132
    elseif title == "Journal's Final Pages" then
        return 2942
    elseif title == "Jubilee Cake Voucher" then
        return 6215
    elseif title == "Keeper's Letter" then
        return 4851
    elseif title == "Khasaad's Treasure Map" then
        return 727
    elseif title == "Laughing Moons Ledger" then
        return 1566
    elseif title == "Legend of Shalug the Shark" then
        return 3285
    elseif title == "Letter From Ember" then
        return 7292
    elseif title == "Letter From Isobel" then
        return 7293
    elseif title == "Mezhun's Field Journal" then
        return 2796
    elseif title == "Nerulean's Guide to Phantoms Vol. II" then
        return 3049
    elseif title == "Ode to a Torchbug" then
        return 2954
    elseif title == "On Soul Shriven, vol 3" then
        return 1551
    elseif title == "An Orc Weaponsmith In Murkmire, Part 1" then -- real name is "From Wrothgar to Lilmoth: A Smith's Tale, Vol 1"
        return 2850
    elseif title == "An Orc Weaponsmith In Murkmire, Part 2" then -- real name is "From Wrothgar to Lilmoth: A Smith's Tale, Vol 2"
        return 2851
    elseif title == "An Orc Weaponsmith In Murkmire, Part 3" then -- real name is "From Wrothgar to Lilmoth: A Smith's Tale, Vol 3"
        return 2852
    elseif title == "Orcthane's Orders" then
        return 439
    elseif title == "Plea to Maximinus" then
        return 2659
    elseif title == "PRISONER: CLARISSE LAURENT" then
        return 2908
    elseif title == "PRISONER: RAYNOR VANOS" then
        return 2907
    elseif title == "PRISONER: TELENGER" then
        return 2909
    elseif title == "Report on Dominion Activities" then
        return 147
    elseif title == "Royal Messenger's Fate" then
        return 2701
    elseif title == "Seeking New Members!" then
        return 7168
    elseif title == "Six Are the Walking Ways" then
        return 4445
    elseif title == "The Song of the Word" then
        return 4446
    elseif title == "Southern Elsweyr Needs You!" then
        return 5697
    elseif title == "Statue of Amminus Entius" then
        return 3419
    elseif title == "Statue of Cavor Merula" then
        return 3416
    elseif title == "Statue of Justia Desticus" then
        return 3417
    elseif title == "Statue of Rusio Olo" then
        return 3418
    elseif title == "Terran's Notes" then
        return 3047
    elseif title == "The Tomb of Ja'darri" then
        return 5737
    elseif title == "Torag ag Krazak, Uz" then
        return 3089
    elseif title == "Tribute Challengers - Novice Tournament" then
        return 7169
    elseif title == "Ushutha's Journal" then
        return 3162
    elseif title == "The Year 2920, Vol. 28" then
        return 2945
    end

    if not self.globalSavedVars.failedDeprecation then
        self.globalSavedVars.failedDeprecation = { librarianBookId = 100000, bookList = {} }
    end

    for _, book in ipairs(self.globalSavedVars.failedDeprecation.bookList) do
        if book.title == title then
            return book.bookId
        end
    end

    local nextBookId = self.globalSavedVars.failedDeprecation.librarianBookId
    local newFailedDeprecation = { title = title, bookId = nextBookId }
    table.insert(self.globalSavedVars.failedDeprecation.bookList, newFailedDeprecation)
    self.globalSavedVars.failedDeprecation.librarianBookId = nextBookId + 1

    return nextBookId
end

function Librarian:UpdateSavedVariables()
    -- before version 3.0 there was no saveVersion
    -- In this version, the bookId is now used as identifier instead of the title (because several book have the same title)
    if not self.globalSavedVars.saveVersion then
        self.globalSavedVars.saveVersion = 1

        -- let's copy the current save into another file just in case something wrong happen during deprecation
        local backupGlobalSavedVars = ZO_SavedVars:NewAccountWide("Librarian_SavedVariables_Backup", 1, nil, {}, nil)
        backupGlobalSavedVars.books = DeepCopy(self.books)

        -- if this player doesn't know any book yet, no need to go further
        if next(self.books) ~= nil then
            local GetLoreBookInfo = GetLoreBookInfo
            local bookIndexes = {
                categoryIndex = 0,
                collectionIndex = 0,
                bookIndex = 0,
                numCollections = 0,
                totalBooks = 0,
            }
            while true do
                if not GetNextBookIndex(bookIndexes) then
                    break
                end

                local title, _, _, bookId = GetLoreBookInfo(bookIndexes.categoryIndex, bookIndexes.collectionIndex, bookIndexes.bookIndex)
                local book = nil
                for _,bookIt in pairs(self.books) do
                    if bookIt.title == title then
                        book = bookIt
                        break
                    end
                end
                if book then
                    if not book.bookId then
                        book.bookId = bookId
                    else -- there is a collision (2 books with same name) so we add an entry for the new one
                        local newBook = { bookId = bookId, title = title }
                        self:AddBookToGlobalSave(newBook)
                        newBook.timeStamp = book.timeStamp
                    end
                end
            end

            -- now we can erase all the field that are useless (ESO API is performant enough to get these data only when needed)
            -- it will also help keeping the save small and avoid issues with corrupted save
            for _,book in pairs(self.books) do
                if book.bookId then
                    book.title = nil
                    book.body = nil
                    book.medium = nil
                    book.showTitle = nil
                else
                    book.bookId = self:GetBookIdWithTitle(book.title)
                end
                book.wordCount = nil -- it will be computed when building the list (it will allow to switch language and this will still be acurate)
            end
        end
    end

    if not self.localSavedVars.saveVersion then
        self.localSavedVars.saveVersion = 1

        -- let's copy the current save into another file just in case something wrong happen during deprecation
        local backupLocalSavedVars = ZO_SavedVars:New("Librarian_SavedVariables_Backup", 1, nil, {}, nil)
        backupLocalSavedVars.characterBooks = DeepCopy(self.characterBooks)

        local GetLoreBookInfo = GetLoreBookInfo
        local bookIndexes = {
            categoryIndex = 0,
            collectionIndex = 0,
            bookIndex = 0,
            numCollections = 0,
            totalBooks = 0,
        }
        while true do
            if not GetNextBookIndex(bookIndexes) then
                break
            end

            local title, _, known, bookId = GetLoreBookInfo(bookIndexes.categoryIndex, bookIndexes.collectionIndex, bookIndexes.bookIndex)

            if known then
                local characterBook = nil
                for _,characterBookIt in ipairs(self.characterBooks) do
                    if characterBookIt.title == title then
                        characterBook = characterBookIt
                        break
                    end
                end

                if characterBook then
                    if not characterBook.bookId then
                        characterBook.bookId = bookId
                    else
                        local newCharacterBook = { title = title, timeStamp = characterBook.timeStamp, bookId = bookId }
                        table.insert(self.characterBooks, newCharacterBook)
                    end
                end
            end
        end

        -- now we can remove the title from the characterBooks and rely only on the bookId (it saves space)
        for _,characterBook in ipairs(self.characterBooks) do
            if characterBook.bookId then
                characterBook.title = nil
            else
                characterBook.bookId = self:GetBookIdWithTitle(characterBook.title)
            end
        end
    end
end