#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 std::string& path)
{
    mFile = std::make_unique<File>(path);
    mFile->Open(true);
    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();
}
