#include "MidiReader.h"

#include "MidiDocument.h"
#include "ByteUtils.h"
#include "MidiTrack.h"
#include "BinaryStream.h"
#include "FileLogger.h"
#include "MidiElements.h"
#include "MidiTimeAdapter.h"
#include "MidiMetaEventAdapter.h"
#include "MidiChannelEventAdapter.h"

#include <fstream>
#include <iostream>
#include <cstring>
#include <bitset>

MidiReader::MidiReader()
    : mDocument(MidiDocument::Create()),
      mLastChannelEventType(MidiChannelEvent::Type::NONE)
{

}

MidiDocument* MidiReader::getDocument() const
{
    return mDocument.get();
}

bool MidiReader::processHeader()
{
    if(!BinaryStream::checkNextDWord(mFile->getInHandle(), HeaderLabel))
    {
        return false;
    }

    const auto length = BinaryStream::getNextDWord(mFile->getInHandle());
    if(!length)
    {
        return false;
    }

    const auto formatType = BinaryStream::getNextWord(mFile->getInHandle());
    if(!formatType)
    {
        return false;
    }
    mDocument->SetFormatType(*formatType);

    const auto expectedTracks = BinaryStream::getNextWord(mFile->getInHandle());
    if(!expectedTracks)
    {
        return false;
    }
    mDocument->SetExpectedTracks(*expectedTracks);

    MidiTimeDivision timeDivision;
    MidiTimeAdapter::ReadTimeDivision(mFile->getInHandle(), timeDivision);
    mDocument->SetTimeDivision(timeDivision);
    return true;
}

int MidiReader::processEvent(MidiTrack* track)
{
    int timeDelta {0};
    unsigned byteCount {0};
    byteCount += MidiTimeAdapter::ReadEventTimeDelta(mFile->getInHandle(), timeDelta);

    char c;
    mFile->getInHandle()->get(c);
    //std::cout << "Event check: " << std::bitset<8>(c) << std::endl;
    byteCount++;
    if(MidiEvent::IsMetaEvent(c))
    {
        auto event = std::make_unique<MetaMidiEvent>();
        event->SetTimeDelta(timeDelta);
        //std::cout << "Meta event " <<std::endl;
        byteCount += MidiMetaEventAdapter::ReadEvent(mFile->getInHandle(), event.get(), mLastMidiChannel);
        track->AddEvent(std::move(event));
    }
    else if(MidiEvent::IsSysExEvent(c))
    {
        //std::cout << "Sysex event" << std::endl;
    }
    else
    {   // Midi event
        auto event = std::make_unique<MidiChannelEvent>();
        event->SetTimeDelta(timeDelta);
        //std::cout << "Midi event" << std::endl;
        byteCount += MidiChannelEventAdapter::readEvent(mFile->getInHandle(), c, event.get(), mLastChannelEventType);
        track->AddEvent(std::move(event));
    }
    return byteCount;
}

bool MidiReader::processTrackChunk(bool debug)
{
    if(!BinaryStream::checkNextDWord(mFile->getInHandle(), TrackChunkLabel))
    {
        return false;
    }

    const auto chunkSize = BinaryStream::getNextDWord(mFile->getInHandle());
    if(!chunkSize)
    {
        return false;
    }

    unsigned byteCount = 0;
    auto track = std::make_unique<MidiTrack>();
    unsigned iter_count = 0;
    while(byteCount < static_cast<unsigned>(*chunkSize))
    {
        //std::cout << "-------------" << std::endl;
        byteCount += processEvent(track.get());
        //std::cout << "Track byte count: " << byteCount << " of " << *chunkSize << std::endl;
        if(debug && iter_count == 40)
        {
            return true;
        }
        iter_count ++;
    }
    mDocument->AddTrack(std::move(track));
    return true;
}

void MidiReader::read(const Path& path)
{
    mFile = std::make_unique<File>(path);
    mFile->open(File::AccessMode::Read);
    if(!processHeader())
    {
        MLOG_ERROR("Problem processing header");
        return;
    }

    int trackCount = 0;
    if(!processTrackChunk(false))
    {
        MLOG_ERROR("Problem processing track chunk");
        return;
    }
    trackCount++;

    if(!processTrackChunk(true))
    {
        MLOG_ERROR("Problem processing track chunk");
        return;
    }
    trackCount++;

    mFile->close();
}
