/***************************************************************************
 *   Copyright (C) 2005 by David Saxton                                    *
 *   david@bluehaze.org                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

#include "config.h"
#ifndef NO_GPSIM

#ifndef GPSIMPROCESSOR_H
#define GPSIMPROCESSOR_H

#include "sourceline.h"

#include <QMap>
// #include <q3valuevector.h>
#include <QList>
#include <QObject>
#include <QVector>

class DebugLine;
class GpsimProcessor;
class MicroInfo;
class pic_processor; // from gpsim
class Register;
class RegisterMemoryAccess;

typedef QMap<SourceLine, SourceLine> SourceLineMap;
typedef QList<int> IntList;

class DebugLine : public SourceLine
{
public:
    DebugLine();
    /// @param fileName a path to a file in the local filesystem
    DebugLine(const QString &fileName, int line);
    /**
     * Whether or not to break when we reach this line.
     */
    bool isBreakpoint() const
    {
        return m_bIsBreakpoint;
    }
    /**
     * Set whether or not to break when we reach this line.
     */
    void setBreakpoint(bool breakpoint)
    {
        m_bIsBreakpoint = breakpoint;
    }
    /**
     * Used for efficiency purposes by GpsimProcessor. Sets a flag.
     */
    void markAsDeleted()
    {
        m_bMarkedAsDeleted = true;
    }
    /**
     * Used for efficiency purposes by GpsimProcessor.
     */
    bool markedAsDeleted() const
    {
        return m_bMarkedAsDeleted;
    }

protected:
    bool m_bIsBreakpoint;
    bool m_bMarkedAsDeleted;

private:
    DebugLine(const DebugLine &dl);
    DebugLine &operator=(const DebugLine &dl);
};

/**
@short Stores info from gpsim register, used to hide gpsim interface
@author David Saxton
*/
class RegisterInfo : public QObject
{
    Q_OBJECT
public:
    RegisterInfo(Register *reg);

    enum RegisterType { Invalid, Generic, File, SFR, Breakpoint };

    RegisterType type() const
    {
        return m_type;
    }
    QString name() const
    {
        return m_name;
    }
    unsigned value() const;
    static QString toString(RegisterType type);

    /**
     * Checks to see if the value has changed; if so, emit new value.
     */
    void update();

signals:
    void valueChanged(unsigned newValue);

protected:
    QString m_name;
    RegisterType m_type;
    Register *m_pRegister;
    unsigned m_prevEmitValue;
};

/**
@short Stores information about a set of registers, used to hide gpsim interface.
@author David Saxton
*/
class RegisterSet
{
public:
    RegisterSet(pic_processor *picProcessor);
    ~RegisterSet();

    /**
     * Calls update for each RegisterInfo in this set.
     */
    void update();
    /**
     * Returns the number of registers.
     */
    unsigned size() const
    {
        return m_registers.size();
    }

    RegisterInfo *fromAddress(unsigned address);
    RegisterInfo *fromName(const QString &name);

protected:
    typedef QMap<QString, RegisterInfo *> RegisterInfoMap;
    RegisterInfoMap m_nameToRegisterMap;
    QVector<RegisterInfo *> m_registers;
};

/**
@author David Saxton
*/
class GpsimDebugger : public QObject
{
    friend class GpsimProcessor;
    Q_OBJECT

public:
    enum Type { AsmDebugger = 0, HLLDebugger = 1 };

    GpsimDebugger(Type type, GpsimProcessor *gpsim);
    ~GpsimDebugger() override;

    GpsimProcessor *gpsim() const
    {
        return m_pGpsim;
    }

    /**
     * When an assembly file was generated by a high level language compiler
     * like SDCC, it will insert markers like ";#CSRC" that show which line
     * of source-code generated the given set of assembly instructions. This
     * matches up the assembly file lines with the associated source file
     * lines.
     * @param sourceFile the path to an assembly file in the local filesystem
     * @param assemblyFile the path to a source file in the local filesystem
     */
    void associateLine(const QString &sourceFile, int sourceLine, const QString &assemblyFile, int assemblyLine);
    /**
     * Check to see if we've hit a breakpoint or similar; if so, this
     * function will stop the execution of the PIC program.
     */
    void checkForBreak();
    /**
     * Sets the breakpoints used for the given file to exactly those that
     * are contained in this list. Breakpoints for other files are not
     * affected.
     * @param path the location of the file (which gpsim must recognise).
     */
    void setBreakpoints(const QString &path, const IntList &lines);
    /**
     * Sets / removes the breakpoint at the given line
     */
    void setBreakpoint(const QString &path, int line, bool isBreakpoint);
    /**
     * Returns the current source line that gpsim is at. By default, this
     * will be the corresponding assembly line. That can be overwritten
     * using mapAddressBlockToLine.
     */
    SourceLine currentLine();
    /**
     * Returns a pointer to the debug info for the current line.
     */
    DebugLine *currentDebugLine();
    /**
     * @return the program address for the given line (or -1 if no such
     * line).
     */
    int programAddress(const QString &path, int line);
    /**
     * Step into the next program line.
     */
    void stepInto();
    /**
     * Step over the next program instruction. If we are currently running,
     * this function will do nothing. Otherwise, it will record the current
     * stack level, step, and if the new stack level is <= the initial level
     * then return - otherwise, this processor will set a breakpoint for
     * stack levels <= initial, and go to running mode.
     */
    void stepOver();
    /**
     * Similar to stepOver, except we break when the stack level becomes <
     * the initial stack level (instead of <= initial).
     */
    void stepOut();

signals:
    /**
     * Emitted when a line is reached. By default, this is the line of the
     * input assembly file; however, the line associated with an address in
     * the PIC memory can be changed with mapAddressBlockToLine.
     */
    void lineReached(const SourceLine &sourceLine);

protected slots:
    void gpsimRunningStatusChanged(bool isRunning);

protected:
    void initAddressToLineMap();
    void stackStep(int dl);
    void emitLineReached();

    int m_stackLevelLowerBreak;      // Set by step-over, for when the stack level decreases to the one given
    SourceLine m_previousAtLineEmit; // Used for working out whether we should emit a new line reached signal
    DebugLine **m_addressToLineMap;
    DebugLine *m_pBreakFromOldLine;
    GpsimProcessor *m_pGpsim;
    Type m_type;
    unsigned m_addressSize;
    SourceLineMap m_sourceLineMap; // assembly <--> High level language
};

/**
@author David Saxton
*/
class GpsimProcessor : public QObject
{
    friend class GpsimDebugger;
    Q_OBJECT

public:
    /**
     * Create a new gpsim processor. After calling this constructor, you
     * should always call codLoadStatus() to ensure that the cod file was
     * loaded successfully.
     */
    GpsimProcessor(QString symbolFile, QObject *parent = nullptr);
    ~GpsimProcessor() override;

    void setDebugMode(GpsimDebugger::Type mode)
    {
        m_debugMode = mode;
    }
    GpsimDebugger *currentDebugger() const
    {
        return m_pDebugger[m_debugMode];
    }

    enum CodLoadStatus {
        CodSuccess,
        CodFileNotFound,
        CodUnrecognizedProcessor,
        CodFileNameTooLong,
        CodLstNotFound,
        CodBadFile,
        CodFileUnreadable,
        CodFailure,
        CodUnknown // Should never be this, but just in case load_symbol_file returns something funny
    };

    enum InstructionType { LiteralOp, BitOp, RegisterOp, UnknownOp };

    /**
     * @return status of opening the COD file
     * @see displayCodLoadStatus
     */
    CodLoadStatus codLoadStatus() const
    {
        return m_codLoadStatus;
    }
    /**
     * Popups a messagebox to the user according to the CodLoadStatus. Will
     * only popup a messagebox if the CodLoadStatus wasn't CodSuccess.
     */
    void displayCodLoadStatus();
    /**
     * Returns a list of source files for the currently running program.
     * Each entry is a path in the local filesystem.
     */
    QStringList sourceFileList();
    /**
     * Set whether or not to run gpsim. (i.e. whether or not the step
     * function should do anything when called with force=false).
     */
    void setRunning(bool run);
    /**
     * Returns true if running (currently simulating), else gpsim is paused.
     */
    bool isRunning() const
    {
        return m_bIsRunning;
    }
    /**
     * Execute the next program instruction. If we are not in a running
     * mode, then this function will do nothing.
     */
    void executeNext();
    /**
     * Reset all parts of the simulation. Gpsim will not run until
     * setRunning(true) is called. Breakpoints are not affected.
     */
    void reset();
    /**
     * Returns the microinfo describing this processor.
     */
    MicroInfo *microInfo() const;

    pic_processor *picProcessor() const
    {
        return m_pPicProcessor;
    }
    unsigned programMemorySize() const;
    RegisterSet *registerMemory() const
    {
        return m_pRegisterMemory;
    }
    /**
     * @return the instruction type at the given address.
     */
    InstructionType instructionType(unsigned address);
    /**
     * @return the address of the operand's register at address if the
     * instruction at address is a register operation, and -1 otherwise.
     */
    int operandRegister(unsigned address);
    /**
     * @return the literal if the instruction at address is a literal
     * operation, and -1 otherwise.
     */
    int operandLiteral(unsigned address);

    // BEGIN Convenience functions for PIC files
    enum ProgramFileValidity { DoesntExist, IncorrectType, Valid };
    /**
     * @return information on the validity of the given program file (either
     * DoesntExist, IncorrectType, or Valid).
     * @see static QString generateSymbolFile
     */
    static ProgramFileValidity isValidProgramFile(const QString &programFile);
    /**
     * Converts the file at programFile to a Symbol file for emulation,
     * and returns that symbol file's path
     * @param fileName The full url to the file
     * @param receiver The slot to connect the assembled signal to
     * @see static bool isValidProgramFile( const QString &programFile )
     */
    static QString generateSymbolFile(const QString &fileName, QObject *receiver, const char *successMember, const char *failMember = nullptr);
    /**
     *Compile microbe to output to the given filename
     */
    static void compileMicrobe(const QString &filename, QObject *receiver, const char *successMember, const char *failMember = nullptr);
    // END convenience functions for PIC files

signals:
    /**
     * Emitted when the running status of gpsim changes.
     */
    void runningStatusChanged(bool isRunning);

protected:
    /**
     * Calls emitLineReached for each debugger.
     */
    void emitLineReached();

    pic_processor *m_pPicProcessor;
    CodLoadStatus m_codLoadStatus;
    const QString m_symbolFile;
    RegisterSet *m_pRegisterMemory;
    GpsimDebugger::Type m_debugMode;
    GpsimDebugger *m_pDebugger[2]; // Asm, HLL

    /**
     * We are called effectively for each cycle of the cycle of the
     * processor. This value is used as some instructions (e.g. goto) take
     * two cycles to execute, and so we must ignore one cycle to ensure
     * realtime simulation.
     */
    bool m_bCanExecuteNextCycle;

private:
    bool m_bIsRunning;
};

#endif

#endif // !NO_GPSIM
