/*========================== begin_copyright_notice ============================

Copyright (C) 2017-2021 Intel Corporation

SPDX-License-Identifier: MIT

============================= end_copyright_notice ===========================*/

#ifndef _IGA_FORMATTER
#define _IGA_FORMATTER

#include "Floats.hpp"
#include "../ErrorHandler.hpp"
#include "../IR/DUAnalysis.hpp"
#include "../IR/Kernel.hpp"
#include "../strings.hpp"

#include <iomanip>
#include <ostream>
#include <string>
#include <sstream>

namespace iga {
    //////////////////////////////////////////////////////////////////////////
    // EXTERNAL INTERFACES
    //////////////////////////////////////////////////////////////////////////

    typedef const char  *(*LabelerFunction)(int32_t, void *);

    // Formatter options
    struct FormatOpts {
        const Model      &model;
        LabelerFunction   labeler = nullptr;
        void             *labelerContext = nullptr;
        bool              numericLabels = false;
        bool              hexFloats = true;
        bool              printInstPc = false;
        bool              syntaxExtensions = false;
        bool              syntaxBfnSymbolicFunctions = true;
        // swsb encoding mode, if not specified, the encoding mode will be
        // derived from platform by SWSB::getEncodeMode
        SWSB_ENCODE_MODE  swsbEncodingMode = SWSB_ENCODE_MODE::SWSBInvalidMode;
        bool              printInstDeps = false;
        bool              printInstDefs = false;
        bool              printInstBits = true;
        bool              printLdSt = false;
        bool              printAnsi = false;
        bool              printJson = false;
        DepAnalysis      *liveAnalysis = nullptr;

        // format with default labels
        FormatOpts(const Model &m)
            : model(m)
        {
        }

        // format with all parameters settable
        // requires at least a context
        FormatOpts(
            const Model &m,
            LabelerFunction _labeler,
            void *_labelerCtx = nullptr,
            bool _numericLabels = false,
            bool _printInstPc = false,
            bool _hexFloats = true,
            bool _syntaxExtensions = false)
            : model(m)
            , labeler(_labeler)
            , labelerContext(_labelerCtx)
            , numericLabels(_numericLabels)
            , hexFloats(_hexFloats)
            , printInstPc(_printInstPc)
            , syntaxExtensions(_syntaxExtensions)
        {
        }

        void setSWSBEncodingMode(SWSB_ENCODE_MODE mode) {
            swsbEncodingMode = mode;
        }

        // in iga.cpp
        void addApiOpts(uint32_t fmtOpts);
    };


    void FormatKernel(
        ErrorHandler &e,
        std::ostream &o,
        const FormatOpts &opts,
        const Kernel &k,
        const void *bits = nullptr);

    void FormatInstruction(
        ErrorHandler &e,
        std::ostream &o,
        const FormatOpts &opts,
        const Instruction &i,
        const void *bits = nullptr);


#ifndef IGA_DISABLE_ENCODER_EXCEPTIONS
    // this uses the decoder, which uses exceptions
    // but only the IGA tester needs this; so we can ifdef it out
    void FormatInstruction(
        ErrorHandler &e,
        std::ostream &o,
        const FormatOpts &opts,
        size_t startPc,
        const void *bits,
        bool useNativeDecoder = false);
    void FormatInstruction(
        ErrorHandler &e,
        std::ostream &o,
        const FormatOpts &opts,
        const void *bits);
#endif // IGA_DISABLE_ENCODER_EXCEPTIONS

    void GetDefaultLabelName(
        std::ostream &o,
        int32_t pc);

    /////////////////////////////////////////////////////////
    // Stuff mainly for testing and debugging.

    // for debugging, shows the opcode and bytes
    std::string FormatOpBits(const iga::Model &m, const void *bits);
    // decodes the op for this platform
    std::string FormatOpName(const iga::Model &m, const void *bits);


    //////////////////////////////////////////////////////////////////////////
    // Used by other modules internal to this directory
    //////////////////////////////////////////////////////////////////////////

    // This wraps a constant ansi escape sequence in a unique type so
    // template functions can handle them different from (const char *)'s.
    // We define emitT(ansi_esc) to not count the characters as part of the
    // current column (for column alignment).
    struct ansi_esc {
        const char *esc;
        constexpr ansi_esc() : ansi_esc(nullptr) {}
        constexpr ansi_esc(const char *_seq) : esc(_seq) {}
    };

    // This wraps a value of some sort to be emitted with an ANSI escape
    // sequence.  We emit this and an ANSI_RESET afterwards.
    // E.g. emit(ansi_span<int>("\033[1;32m",0x12))
    template <typename T>
    struct ansi_span {
        ansi_esc esc;
        const T &val;
        constexpr ansi_span(const T &_val)
            : ansi_span(nullptr, _val) { }
        constexpr ansi_span(const char *_seq, const T &_val)
            : esc(_seq), val(_val) { }
    };


    // The abstract implementation of a formatter that has a notion of
    // column alignment and some other basic, language-agnostic constructs.
    class BasicFormatter {
        size_t           currColCapacity; // preferred size of current column
        size_t           currColSize; // current col's size
        size_t           currLineDebt; // sum total of the column overflow
                                      // (later cols can pay for overflow
                                      // to realign future columns)
        bool             printAnsi;

    protected:
        ansi_esc ANSI_RESET;
        // subclasses to this class may define other ansi sequences

        std::ostream&    o;

        BasicFormatter(bool _printAnsi, std::ostream &out) :
            printAnsi(_printAnsi),
            currColCapacity((size_t)-1),
            currColSize(0),
            currLineDebt(0),
            o(out)
        {
            // TODO: could make these mappable via environment variable
            // export IGA_FormatAnsiRegisterArf="\033[38;2;138;43;211m"
            // export IGA_FormatAnsiMnemonic=...
            // export IGA_FormatAnsiComment=...
            if (printAnsi) {
                ANSI_RESET          = "\033[0m";
            }
        }

    public:
        // start or finish a padded column
        void startColumn(int len) {
            IGA_ASSERT(len >= 0, "negative column size");
            IGA_ASSERT(currColCapacity == (size_t)-1,
                "startColumn() called from with-in column "
                "(missing finishColumn())");
            currColCapacity = (size_t)len;
            currColSize = 0;
        }
        void finishColumn() {
            IGA_ASSERT(currColCapacity != (size_t)-1,
                "finishColumn() called with no active column "
                "(missing startColumn(...))");
            //
            if (currColSize <= currColCapacity) {
                // we underflowed; we have space left;
                // if there is debt, we can cover some of it
                size_t padding = currColCapacity - currColSize;
                if (currLineDebt > 0) {
                    size_t correction =
                        std::min<size_t>(currLineDebt, padding);
                    padding -= correction;
                    currLineDebt -= correction;
                }
                emitSpaces(padding);
            } else if (currColSize > currColCapacity) {
                // we overflowed accummulate debt
                currLineDebt += currColSize - currColCapacity;
            }
            //
            currColCapacity = (size_t)-1;
            currColSize = 0;
        }


        void newline() {
            IGA_ASSERT(currColCapacity == (size_t)-1,
                "column open at newline");
            emit('\n');
            currLineDebt = 0;
        }


        template <typename T>
        void emitT(ansi_span<T> t) {
            emitT(t.esc);
            emitT(t.value);
            emitT(ANSI_RESET);
        }
        void emitT(ansi_esc e) {
            // specialize this instance to not count the ANSI escapes as
            // output characters (size of column)
            if (e.esc)
                o << e.esc;
        }
        template <typename T>
        void emitT(const T &t) {
            size_t n = (size_t)o.tellp();
            o << t;
            currColSize += (size_t)o.tellp() - n;
        }


        // enables you emit a list of things
        //
        // emit(foo, bar, baz, ...);
        template <typename T, typename...Ts>
        void emit(T t) {
            emitT(t);
        }
        template <typename T, typename...Ts>
        void emit(T t, Ts...ts) {
            emitT(t);
            emit(ts...);
        }

        // emits an sequence conditionally surrounded by an ANSI color
        template <typename T, typename...Ts>
        void emitAnsi(bool z, ansi_esc esc, T t0, Ts...ts) {
            if (z)
                emitT(esc);

            emit(t0, ts...);

            if (z)
                emitT(ANSI_RESET);
        }
        template <typename T, typename...Ts>
        void emitAnsi(ansi_esc esc, T t, Ts...ts) {
            emitAnsi(true, esc, t, ts...);
        }

        void emitSpaces(size_t n) {
            for (size_t i = 0; i < n; i++)
                o << ' ';
            currColSize += n;
        }


        template <typename T>
        void emitDecimal(const T &t) {
            o << std::dec << t;
        }


        template <typename T>
        void emitHex(const T &t, int cw = 0) {
            fmtHex(o, (uint64_t)t, cw);
            o << std::dec;
        }

        template <typename T>
        void emitHexDigits(const T &t, int cw = 0) {
            fmtHexDigits(o, (uint64_t)t, cw);
            o << std::dec;
        }


        template <typename T>
        void emitBinary(const T &t) {
            emit("0b");
            std::stringstream ss;
            T bits = t;
            while (bits != 0) {
                emit((bits & 0x1) ? '1' : '0');
                bits >>= 1;
            }
            std::string str = ss.str();
            for (int i = (int)str.length() - 1; i >= 0; i--) {
                emit(str[i]);
            }
        }


        template <typename T>
        void emitFloat(const T &f) {
            FormatFloat(o, f);
        }
    };


    struct ColumnPreferences {
        int predication;
        int opCodeExecInfo;
        int flagMod;
        int dstOp;
        int srcOp;
        int sendDstOp; // we give send ops less space than normal
        int sendSrcOp; //we give send ops less space than norma
        int sendDesc;
        ColumnPreferences()
            // enough space for (~f0.0) or (W), (W&~f0.0) overflows
            : predication(7)
            // sends (16|M0)
            // madm (16|M16)
            , opCodeExecInfo(13)
            // (le)f0.0
            , flagMod(9)
            // r120.11<1>:hf
            , dstOp(12)
            , srcOp(16)
            , sendDstOp(7) // null:ud
                           // r120.4<4;4,1>:ub
            , sendSrcOp(6) // r120#1
            , sendDesc(10)
        {
        }
    };
} //end: namespace iga*

#endif
