/***************************************************************************
 *   Copyright (C) 2012 by Santiago González                               *
 *                                                                         *
 ***( see copyright.txt file at root folder )*******************************/

#include <QFileInfo>

#include "basedebugger.h"
#include "editorwindow.h"
#include "simulator.h"
#include "cpu8bits.h"
#include "mcu.h"
#include "utils.h"

#define tr(str) QCoreApplication::translate("BaseDebugger",str)

BaseDebugger::BaseDebugger( CodeEditor* parent, OutPanelText* outPane )
            : Compiler( parent, outPane )
{
    m_compName = "None"; 
    m_langLevel = 0; 
    m_lstType = 0; 

    m_appPath = QCoreApplication::applicationDirPath(); 
}
BaseDebugger::~BaseDebugger( )
{
    if( eMcu::self() ) eMcu::self()->getRamTable()->remDebugger( this ); 
}

bool BaseDebugger::upload()
{
    if( !m_firmware.isEmpty() && !QFileInfo::exists( m_firmware ) )
    {
        m_outPane->appendLine( "\n"+tr("Error: Hex file doesn't exist:")+"\n"+m_firmware ); 
        return false; 
    }
    if( !Mcu::self() )
    {
        m_outPane->appendLine( "\n"+tr("Error: No Mcu in Simulator... ") ); 
        return false; 
    }
    bool ok = true; 
    if( !m_firmware.isEmpty() )
    {
        ok = Mcu::self()->load( m_firmware ); 
        if( ok ) m_outPane->appendText( "\n"+tr("FirmWare Uploaded to ") ); 
        else     m_outPane->appendText( "\n"+tr("Error uploading firmware to ") ); 
        m_outPane->appendLine( Mcu::self()->idLabel()+"("+ Mcu::self()->device() +")" ); 
        m_outPane->appendLine( m_firmware+"\n" ); 
    }
    if( ok ){
        m_debugStep = false; 
        m_stepOver = false; 
        m_running = false; 
        eMcu::self()->setDebugger( this ); 
        if( m_fileExt != ".hex" ) ok = postProcess(); 
    }
    return ok; 
}

void BaseDebugger::preProcess()
{
    QStringList lines = fileToStringList( m_file, "BaseDebugger::preProcess" ); 
    m_codeStart = 0; 
}

bool BaseDebugger::postProcess()
{
    //m_lastLine = 0; 
    m_flashToSource.clear(); 
    //m_sourceToFlash.clear(); 

    QString lstFile = m_buildPath+m_fileName+".lst"; 
    if( !QFileInfo::exists( lstFile ) )
    {
        m_outPane->appendLine( "\n"+tr("Warning: lst file doesn't exist:")+"\n"+lstFile ); 
        return false; 
    }
    m_outPane->appendText( "\nMapping Flash to Source... " ); 
    QString srcFile = m_fileDir + m_fileName + m_fileExt; 
    QStringList srcLines = fileToStringList( srcFile, "BaseDebugger::postProcess" ); 
    QStringList lstLines = fileToStringList( lstFile, "BaseDebugger::postProcess" ); 

    QString lstLine; 
    int lstLineNumber = 0; 
    int srcLineNumber = 0; 
    int listFileSize = lstLines.size(); 
    int lastListLine = 0; 

    if( m_langLevel ) // High level language
    {
        QString file = m_fileName+m_fileExt; 
        bool found = false; 
        for( QString lstLine : lstLines )
        {
            lstLineNumber++; 
            if( found )
            {
                found = false; 
                if( m_lstType & 1 ) lstLine = lstLine.split(":").last(); 
                QStringList words = lstLine.split(" "); 
                words.removeAll(""); 
                if( words.size() < 4 ) continue; 

                bool ok = false; 
                int addrIndex = (m_lstType & 2)>>1; 
                if( addrIndex )  // check if line valid: first item should be a number
                {
                    words.at( 0 ).toInt( &ok, 16 ); 
                    if( !ok ) continue; 
                }
                lstLine = words.at( addrIndex ); 
                int address = lstLine.toInt( &ok, 16 ); 
                if( ok ){
                    setLineToFlash( {srcFile, srcLineNumber}, m_codeStart+address ); 
                    continue; 
                }
            }
            for( QString sFile : m_fileList )  // Search in all files
            {
                if( !lstLine.contains( file ) ) continue; 

                QString str = lstLine.split( file ).takeLast(); 
                QStringList words = str.remove(":").split(" "); 
                words.removeAll(""); 
                if( words.isEmpty() ) continue; 
                str = words.first(); 

                srcLineNumber = str.toInt( &found );  // src line number found
                if( found ){
                    srcFile = sFile; 
                    break; 
                }
            }
        }
    }
    else{                        // asm
        QString funcName; 
        for( QString srcLine : srcLines ) // Get Functions
        {
            if( isNoValid( srcLine ) ) continue; 
            srcLine = srcLine.split("; ").first(); 
            if( !srcLine.toUpper().contains("CALL") ) continue; 
            srcLine = srcLine.replace("\t", " "); 
            QStringList l = srcLine.split(" "); 
            l.removeAll(""); 
            funcName = l.last(); 
            if( !funcName.isEmpty() ) m_functions[funcName.toUpper()] = -1; 
        }

        for( QString srcLine : srcLines )
        {
            srcLineNumber++; 
            srcLine = srcLine.replace("\t", " ").remove(" "); 
            if( isNoValid( srcLine ) ) continue; 
            srcLine = srcLine.split("; ").first(); 
            lastListLine = lstLineNumber; 

            while( true )
            {
                if( ++lstLineNumber >= listFileSize ) break;    // End of lst file
                lstLine = lstLines.at( lstLineNumber-1 ); 
                lstLine = lstLine.replace("\t", " "); 
                if( isNoValid( lstLine ) ) continue; 

                lstLine = lstLine.split("; ").first(); 
                QString line = lstLine; 
                line = line.remove(" "); 
                if( line.contains( srcLine ) ) break;           // Line found
            }
            if( lstLineNumber >= listFileSize )
            {
                lstLineNumber = lastListLine;  /// lstLineNumber = 0; 
                continue; 
            }
            if( m_lstType & 1 )
            {
                QStringList l = lstLine.split(":"); 
                if( l.size() > 1 ) lstLine = lstLine.split(":").at( 1 ); 
                else continue; 
            }
            QStringList words = lstLine.split(" "); 
            words.removeAll(""); 

            if( words.size() < 3 ){
                if( srcLine.contains( ":" ) )                 // Find Subroutines
                {
                    funcName = srcLine.left( srcLine.indexOf(":") ).toUpper(); 
                    if( !m_functions.contains( funcName ) ) funcName = ""; 
                }
                continue; 
            }
            int index = (m_lstType & 2)>>1 ; 
            bool ok = false; 
            words.at( index+1 ).toInt( &ok, 16 );  // Avoid things like "8: E = %10000000" (vasm)
            if( !ok ) continue; 

            int address = m_codeStart+words.at( index ).toInt( &ok, 16 ); 
            if( ok )
            {
                setLineToFlash( {srcFile, srcLineNumber}, address ); 
                if( !funcName.isEmpty() )                  // Subroutine starting here
                {
                    m_functions[funcName] = address; 
                    funcName = ""; 
                }
            }
        }
    }
    m_outPane->appendLine( QString::number( m_flashToSource.size() )+" lines mapped" ); 
    return true; 
}

void BaseDebugger::run()
{
    m_running = true; 
    stepFromLine(); 
}

void BaseDebugger::pause()
{
    m_running = false; 
    m_debugStep = false; 
    EditorWindow::self()->pauseAt( m_prevLine ); 
}

bool BaseDebugger::stepFromLine( bool over )
{
    bool ok = true; 
    if( m_prevLine.lineNumber == -1 ) // Jump from line 1 to flash addr = 0
    {
        if( m_flashToSource.keys().contains(0) )
        {
            codeLine_t l = m_flashToSource.value( 0 ); 
            m_prevLine = l; 
            EditorWindow::self()->pauseAt( m_prevLine ); 
            ok = false; 
        }
    }
    m_over = over; 
    m_exitPC = 0; 
    m_debugStep = true; 

    return ok; 
}

void BaseDebugger::stepDebug()
{
    if( !m_debugStep ) return; 

    int lastPC = eMcu::self()->cpu()->getPC(); 
    eMcu::self()->stepCpu(); 
    int PC = eMcu::self()->cpu()->getPC(); 

    if( lastPC != PC )
    {
        if( m_over ){                                 // Step Over entry
            if( m_functions.values().contains( PC ) )
            {
                m_exitPC = eMcu::self()->cpu()->RET_ADDR(); 
                m_over = false; 
                if( PC == m_exitPC ) m_exitPC = 0; 
                else return; 
            }
        }
        if( m_exitPC )                               // Step Over exit
        {
            if( PC == m_exitPC ) m_exitPC = 0; 
            else return; 
        }
        if( m_flashToSource.contains( PC ) )
        {
            codeLine_t line = m_flashToSource.value( PC ); 
            if( line != m_prevLine )
            {
                m_prevLine = line; 
                EditorWindow::self()->lineReached( line ); 
}   }   }   }

QString BaseDebugger::getValueInFile( QString line, QString key ) // Static
{
    QString lineL = line.toLower(); 
    if( !lineL.contains( key.toLower() ) ) return ""; 

    QString value = ""; 
    QStringList wordList = line.replace("=", " ").split(" "); 

    while( wordList.size() > 1 )
    {
        QString word = wordList.takeFirst(); 
        if( word.isEmpty() ) continue; 
        if( word.toLower().contains( key ) )
        {
            while( !wordList.isEmpty() && value.isEmpty() )
                value = wordList.takeFirst(); 
            value = value.remove(",").remove(".").remove("; "); 
    }   }
    return value; 
}

bool BaseDebugger::isNoValid( QString line )
{
    return (  line.isEmpty()
           || line.startsWith("void")
           || line.startsWith("{")
           || line.startsWith("}")
           || line.startsWith("//")
           || line.startsWith("/*")
           || line.startsWith("; ")
           || line.startsWith("#")
           || line.startsWith(".") ); 
}

void BaseDebugger::setLineToFlash( codeLine_t line, int addr )
{
    //int lineNumber = line.lineNumber; 
    if( !m_flashToSource.contains( addr ) )
    {
        //qDebug() << " line:" << line << "addr:" << addr; 
        //if( lineNumber > m_lastLine ) m_lastLine = lineNumber; 
        m_flashToSource[ addr ] = line; 
        ///qDebug() <<addr<<line.lineNumber<<line.file; 
        //m_sourceToFlash[ lineNumber ] = addr; 
    }
}

int BaseDebugger::getValidLine( codeLine_t line )
{
    int lineNumber = line.lineNumber; 
    int lastLine = 0; 
    QList<int> lineList; 
    for( codeLine_t cd : m_flashToSource.values() ){
        if( line.file == cd.file )
        {
            if( cd.lineNumber > lastLine ) lastLine = cd.lineNumber; 
            lineList.append( cd.lineNumber ); 
        }
    }
    while( !lineList.contains( lineNumber ) && lineNumber<=lastLine ) lineNumber++; 
    if( lineNumber == lastLine && !lineList.contains( lineNumber ) ) return -1;  // No valid line found
    return lineNumber; 
}

bool BaseDebugger::isMappedLine( codeLine_t line )
{
    for( codeLine_t codeLine : m_flashToSource.values() )
        if( codeLine == line ) return true; 

    return false; 
}

QString BaseDebugger::getVarType( QString var )
{
    return m_varTypes.value( var ); 
}

//#include "moc_basedebugger.cpp"
