#include "DirectoryNode.h"

DirectoryNode::DirectoryNode(const String &nodeName)
    : name(nodeName), currentPlaying(nullptr)
{
    id = DirectoryNode::idCounter;
    DirectoryNode::idCounter++;
}

DirectoryNode::~DirectoryNode()
{
    for (DirectoryNode *childNode : subdirectories)
    {
        delete childNode;
    }
}

const uint16_t DirectoryNode::getId() const
{
    return id;
}

const String &DirectoryNode::getName() const
{
    return name;
}

const std::vector<DirectoryNode *> &DirectoryNode::getSubdirectories() const
{
    return subdirectories;
}

const std::vector<String> &DirectoryNode::getMP3Files() const
{
    return mp3Files;
}

void DirectoryNode::setCurrentPlaying(const String *mp3File)
{
    currentPlaying = mp3File;
    for (int i = 0; i < mp3Files.size(); i++)
    {
        if (mp3Files[i] == *mp3File && ids.size() > i)
        {
            currentPlayingId = ids[i];
        }
    }
}

const String *DirectoryNode::getCurrentPlaying() const
{
    return currentPlaying;
}

const uint16_t DirectoryNode::getCurrentPlayingId() const
{
    return currentPlayingId;
}

uint16_t DirectoryNode::getNextId()
{
    uint16_t next = DirectoryNode::idCounter;
    DirectoryNode::idCounter++;
    return next;
}

void DirectoryNode::addSubdirectory(DirectoryNode *subdirectory)
{
    subdirectories.push_back(subdirectory);
}

void DirectoryNode::addMP3File(const String &mp3File)
{
    mp3Files.push_back(mp3File);
    ids.push_back(getNextId());
}

void DirectoryNode::setSecondsPlayed(const uint32_t seconds)
{
    secondsPlayed = seconds;
}

uint32_t DirectoryNode::getSecondsPlayed()
{
    return secondsPlayed;
}

void DirectoryNode::buildDirectoryTree(const char *currentPath)
{
    // Clear existing data to prevent duplicates when rebuilding
    for (DirectoryNode *childNode : subdirectories)
    {
        delete childNode;
    }
    subdirectories.clear();
    mp3Files.clear();
    ids.clear();
    
    File rootDir = SD.open(currentPath);
    while (true)
    {
        File entry = rootDir.openNextFile();
        if (!entry)
        {
            break;
        }

        if (entry.isDirectory() && entry.name()[0] != '.' && strcmp(entry.name(), sys_dir.c_str()))
        {
            DirectoryNode *newNode = new DirectoryNode(entry.name());
            subdirectories.push_back(newNode);
            newNode->buildDirectoryTree((String(currentPath) + entry.name()).c_str());
        }
        else if (String(entry.name()).endsWith(".mp3")||String(entry.name()).endsWith(".MP3"))
        {
            mp3Files.push_back(entry.name());
            ids.push_back(getNextId());
        }
        entry.close();
    }
    rootDir.close();
}

void DirectoryNode::printDirectoryTree(int level) const
{
    for (int i = 0; i < level; i++)
    {
        Serial.print("  ");
    }
    Serial.println(name);

    for (const String &mp3File : mp3Files)
    {
        for (int i = 0; i <= level; i++)
        {
            Serial.print("  ");
        }
        Serial.println(mp3File);
    }

    for (DirectoryNode *childNode : subdirectories)
    {
        childNode->printDirectoryTree(level + 1);
    }
}

DirectoryNode *DirectoryNode::findFirstDirectoryWithMP3s()
{
    if (!mp3Files.empty())
    {
        // Found a directory with MP3 files
        return this;
    }

    for (DirectoryNode *subdirectory : subdirectories)
    {
        DirectoryNode *result = subdirectory->findFirstDirectoryWithMP3s();
        if (result != nullptr)
        {
            // Found a directory with MP3 files in the subdirectories
            return result;
        }
    }

    // No directory with MP3 files found
    return nullptr;
}

void DirectoryNode::advanceToFirstMP3InThisNode()
{
    if (mp3Files.size() > 0)
    {
        setCurrentPlaying(&mp3Files[0]);
    }
}

DirectoryNode *DirectoryNode::advanceToMP3(const uint16_t id)
{


    for (size_t i = 0; i < ids.size(); i++)
        {
            if (id == ids[i])
            {
                // Found the current MP3 file
                currentPlaying = &mp3Files[i];
                currentPlayingId = id;
                return this;
            }
        }

    for (auto subdir : subdirectories)
    {
        if (subdir->getId() == id)
        {
            subdir->advanceToFirstMP3InThisNode();
            return subdir;
        }

        // Have each subdirectory advance its song
        for (size_t i = 0; i < subdir->ids.size(); i++)
        {
            if (id == subdir->ids[i])
            {
                // Found the current MP3 file
                subdir->currentPlaying = &subdir->mp3Files[i];
                subdir->currentPlayingId = id;
                return subdir;
            }
        }
    }

    // If we get here, there were no MP3 files or subdirectories left to check
    currentPlaying = nullptr;
    Serial.println("no more nodes found");
    return this;
}

DirectoryNode *DirectoryNode::advanceToMP3(const String *currentGlobal)
{
    for (auto subdir : subdirectories)
    {
        if (subdir->getName() == *currentGlobal)
        {
            subdir->advanceToFirstMP3InThisNode();
            return subdir;
        }

        // Have each subdirectory advance its song
        for (size_t i = 0; i < subdir->mp3Files.size(); i++)
        {
            if (*currentGlobal == subdir->mp3Files[i])
            {
                // Found the current MP3 file
                if (i < subdir->mp3Files.size() - 1)
                {
                    subdir->setCurrentPlaying(&subdir->mp3Files[i]);
                    return subdir;
                }
            }
        }
    }

    // If we get here, there were no MP3 files or subdirectories left to check
    currentPlaying = nullptr;
    Serial.println("no more nodes found");
    return this;
}

/**
 * Moves to the previous MP3 file in the directory.
 * If the current song has been playing for more than a specific threshold, restarts the current song.
 * If the current song has just started, or it's the first song, moves to the previous song in the directory.
 *
 * @param thresholdSeconds The number of seconds to decide whether to restart the current song or go to the previous song.
 * @return A pointer to the DirectoryNode where the new current song is located, or nullptr if there's no previous song.
 */
DirectoryNode *DirectoryNode::goToPreviousMP3(uint32_t thresholdSeconds)
{
    if (secondsPlayed > thresholdSeconds || currentPlaying == nullptr)
    {
        // Restart the current song if it's been playing for more than thresholdSeconds
        // Or if there is no current song (at the start of the list)
        return this;
    }
    else
    {
        // Find the previous song
        for (size_t i = 0; i < mp3Files.size(); i++)
        {
            if (currentPlaying != nullptr && *currentPlaying == mp3Files[i] && i > 0)
            {
                // Move to the previous song
                setCurrentPlaying(&mp3Files[i - 1]);
                return this;
            }
        }

        // If the first song in the directory or no song was playing, move to the previous directory, if any
        for (auto subdir : subdirectories)
        {
            DirectoryNode *previousNode = subdir->goToPreviousMP3(thresholdSeconds);
            if (previousNode != nullptr)
            {
                return previousNode;
            }
        }
    }
    // No previous song available
    return nullptr;
}

DirectoryNode *DirectoryNode::advanceToNextMP3(const String *currentGlobal)
{
    bool useFirst = false;
    Serial.println(currentGlobal->c_str());
    if (currentGlobal != nullptr)
    {
        for (size_t i = 0; i < mp3Files.size(); i++)
        {
            if (*currentGlobal == mp3Files[i])
            {
                // Found the current playing MP3 file
                if (i < mp3Files.size() - 1)
                {
                    // Advance to the next MP3 file in the same directory
                    setCurrentPlaying(&mp3Files[i + 1]);
                    return this;
                }
                useFirst = true;
                // Reached the end of the MP3 files in the directory
                break;
            }
        }
    }

    // We are either not playing, or we've exhausted all the MP3 files in this directory.
    // Therefore, we need to recursively look in our subdirectories.
    for (auto subdir : subdirectories)
    {

        if (useFirst && subdir->mp3Files.size() > 0)
        {
            subdir->setCurrentPlaying(&subdir->mp3Files[0]);
            return subdir;
        }

        // Have each subdirectory advance its song
        for (size_t i = 0; i < subdir->mp3Files.size(); i++)
        {
            if (*currentGlobal == subdir->mp3Files[i])
            {
                // Found the current playing MP3 file
                if (i < subdir->mp3Files.size() - 1)
                {
                    // Advance to the next MP3 file in the same directory
                    subdir->setCurrentPlaying(&subdir->mp3Files[i + 1]);
                    return subdir;
                }
                else
                {
                    useFirst = true;
                }
                // Reached the end of the MP3 files in the directory
                break;
            }
        }
    }

    // If we get here, there were no MP3 files or subdirectories left to check
    currentPlaying = nullptr;
    Serial.println("no more nodes found");
    return this;
}

String DirectoryNode::getDirectoryStructureHTML() const
{
    String html;
    html.reserve(1024); // Reserve memory for better performance
    if (name == "/")
    {
        html += "<ul>\n";
    }

    if (name != "/")
    {
        html += "<li data-id=\"" + String(id) + "\"><b>" + name + "</b></li>\n";
    }

    for (int i = 0; i < mp3Files.size(); i++)
    {
        html += "<li data-id=\"" + String(ids[i]) + "\">" + mp3Files[i] + "</li>\n";
    }

    for (DirectoryNode *childNode : subdirectories)
    {
        html += childNode->getDirectoryStructureHTML();
    }
    if (name == "/")
    {
        html += "</ul>\n";
    }

    return html;
}

void DirectoryNode::appendIndentation(String &html, int level) const
{
    for (int i = 0; i < level; i++)
    {
        html += "  ";
    }
}

String DirectoryNode::getCurrentPlayingFilePath() const
{
    if (currentPlaying != nullptr)
    {
        String filePath = "/" + name;
        if (!filePath.endsWith("/"))
        {
            filePath += "/";
        }
        filePath += *currentPlaying;
        return filePath;
    }
    return "";
}
