// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: File stream wrapper that understands indentation
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************

#ifndef VERILATOR_V3FILE_H_
#define VERILATOR_V3FILE_H_

#include "config_build.h"
#include "verilatedos.h"

#include "V3Error.h"
#include "V3Stats.h"

#include <array>
#include <fstream>
#include <list>
#include <memory>
#include <set>
#include <stack>
#include <vector>

class AstNode;

//============================================================================
// V3File: Create streams, recording dependency information

class V3File final {
public:
    static std::ifstream* new_ifstream(const string& filename) {
        addSrcDepend(filename);
        return new_ifstream_nodepend(filename);
    }
    static std::ifstream* new_ifstream_nodepend(const string& filename) VL_MT_SAFE {
        return new std::ifstream{filename.c_str()};
    }
    static std::ofstream* new_ofstream(const string& filename, bool append = false) {
        addTgtDepend(filename);
        return new_ofstream_nodepend(filename, append);
    }
    static std::ofstream* new_ofstream_nodepend(const string& filename, bool append = false) {
        createMakeDirFor(filename);
        if (append) return new std::ofstream{filename.c_str(), std::ios::app};
        return new std::ofstream{filename.c_str()};
    }
    static FILE* new_fopen_w(const string& filename) {
        createMakeDirFor(filename);
        addTgtDepend(filename);
        return fopen(filename.c_str(), "w");
    }

    // Dependencies
    static void addSrcDepend(const string& filename) VL_MT_SAFE;
    static void addTgtDepend(const string& filename) VL_MT_SAFE;
    static void writeDepend(const string& filename);
    static std::vector<string> getAllDeps();
    static void writeTimes(const string& filename, const string& cmdlineIn);
    static bool checkTimes(const string& filename, const string& cmdlineIn);

    // Directory utilities
    static void createMakeDirFor(const string& filename);
    static void createMakeDir();
};

//============================================================================
// VInFilter: Read a input file, possibly filtering it, and caching contents

class VInFilterImp;

class VInFilter final {
public:
    // TYPES
    using StrList = std::list<std::string>;

private:
    VInFilterImp* m_impp;

    // CONSTRUCTORS
    VL_UNCOPYABLE(VInFilter);

public:
    explicit VInFilter(const string& command);
    ~VInFilter();

    // METHODS
    // Read file contents and return it.  Return true on success.
    bool readWholefile(const string& filename, StrList& outl);
};

//============================================================================
// V3OutFormatter: A class for automatic indentation of output code.

class V3OutFormatter VL_NOT_FINAL {
    // TYPES
public:
    enum AlignClass : uint8_t { AL_AUTO = 0, AL_STATIC = 1 };
    enum Language : uint8_t { LA_C, LA_JSON, LA_MK, LA_VERILOG, LA_XML };

private:
    // MEMBERS
    const Language m_lang;  // Indenting Verilog code
    int m_blockIndent;  // Characters per block indent
    int m_commaWidth;  // Width after which to break at ,'s
    int m_lineno = 1;
    int m_column = 0;
    int m_nobreak = false;  // Basic operator or begin paren, don't break next
    int m_sourceLastLineno = 0;
    int m_sourceLastFilenameno = 0;
    bool m_prependIndent = true;
    bool m_inStringLiteral = false;
    int m_indentLevel = 0;  // Current {} indentation
    std::stack<int> m_parenVec;  // Stack of columns where last ( was
    int m_bracketLevel = 0;  // Indenting = { block, indicates number of {'s seen.

    int endLevels(const char* strg);
    void putcNoTracking(char chr);

public:
    explicit V3OutFormatter(Language lang);
    virtual ~V3OutFormatter() = default;
    // ACCESSORS
    void blockIndent(int flag) { m_blockIndent = flag; }
    // METHODS
    void printf(const char* fmt...) VL_ATTR_PRINTF(2);
    void puts(const char* strg) { putns(nullptr, strg); }
    void puts(const string& strg) { putns(nullptr, strg); }
    void putns(const AstNode* nodep, const char* strg);
    void putns(const AstNode* nodep, const string& strg) { putns(nodep, strg.c_str()); }
    void putsNoTracking(const string& strg);
    void putsQuoted(const string& strg);
    void putBreak();  // Print linebreak if line is too wide
    void putBreakExpr();  // Print linebreak in expression if line is too wide
    void putbs(const char* strg) {
        putBreakExpr();
        putns(nullptr, strg);
    }
    void putbs(const string& strg) {
        putBreakExpr();
        putns(nullptr, strg);
    }
    void putnbs(const AstNode* nodep, const string& strg) {
        putBreakExpr();
        putns(nodep, strg);
    }
    bool exceededWidth() const { return m_column > m_commaWidth; }
    void indentInc() { m_indentLevel += m_blockIndent; }
    void indentDec() {
        m_indentLevel -= m_blockIndent;
        UASSERT(m_indentLevel >= 0, "Underflow of indentation");
    }
    void blockInc() { m_parenVec.push(m_indentLevel + m_blockIndent); }
    void blockDec() {
        if (!m_parenVec.empty()) m_parenVec.pop();
    }
    void ensureNewLine() {
        if (!m_nobreak) puts("\n");
    }
    // STATIC METHODS
    static const std::string& indentSpaces(int num);
    // Add escaped characters to strings
    static string quoteNameControls(const string& namein, Language lang = LA_C) VL_PURE;
    static bool tokenMatch(const char* cp, const char* cmp);
    static bool tokenNotStart(const char* cp);  // Import/export meaning no endfunction
    static bool tokenStart(const char* cp);
    static bool tokenEnd(const char* cp);

    // CALLBACKS - MUST OVERRIDE
    virtual void putcOutput(char chr) = 0;
    virtual void putsOutput(const char* str) = 0;
};

//============================================================================
// V3OutFile: A class for printing to a file, with automatic indentation of C++ code.

class V3OutFile VL_NOT_FINAL : public V3OutFormatter {
    // Size of m_bufferp.
    // 128kB has been experimentally determined to be in the zone of buffer sizes that work best.
    // It is also considered to be the smallest I/O buffer size in GNU coreutils (io_blksize) that
    // allows to best minimize syscall overhead.
    // The hard boundaries are CPU L2/L3 cache size on the top and filesystem block size
    // on the bottom.
    static constexpr std::size_t WRITE_BUFFER_SIZE_BYTES = 128 * 1024;

    // MEMBERS
    const std::string m_filename;
    FILE* m_fp = nullptr;
    std::size_t m_usedBytes = 0;  // Number of bytes stored in m_bufferp
    std::size_t m_writtenBytes = 0;  // Number of bytes written to output
    std::unique_ptr<std::array<char, WRITE_BUFFER_SIZE_BYTES>> m_bufferp;  // Write buffer

public:
    V3OutFile(const string& filename, V3OutFormatter::Language lang);
    V3OutFile(const V3OutFile&) = delete;
    V3OutFile& operator=(const V3OutFile&) = delete;
    V3OutFile(V3OutFile&&) = delete;
    V3OutFile& operator=(V3OutFile&&) = delete;
    ~V3OutFile() override;

    std::string filename() const { return m_filename; }

    void putsForceIncs();

    void statRecordWritten() {
        writeBlock();
        V3Stats::addStatSum(V3Stats::STAT_CPP_CHARS, m_writtenBytes);
    }

private:
    void writeBlock() {
        if (VL_LIKELY(m_usedBytes > 0)) {
            fwrite(m_bufferp->data(), m_usedBytes, 1, m_fp);
            m_writtenBytes += m_usedBytes;
            m_usedBytes = 0;
        }
    }
    // CALLBACKS
    void putcOutput(char chr) override {
        m_bufferp->at(m_usedBytes++) = chr;
        if (VL_UNLIKELY(m_usedBytes >= WRITE_BUFFER_SIZE_BYTES)) writeBlock();
    }
    void putsOutput(const char* str) override {
        std::size_t len = strlen(str);
        std::size_t availableBytes = WRITE_BUFFER_SIZE_BYTES - m_usedBytes;
        while (VL_UNLIKELY(len >= availableBytes)) {
            std::memcpy(m_bufferp->data() + m_usedBytes, str, availableBytes);
            m_usedBytes = WRITE_BUFFER_SIZE_BYTES;
            writeBlock();
            str += availableBytes;
            len -= availableBytes;
            availableBytes = WRITE_BUFFER_SIZE_BYTES;
        }
        if (len > 0) {
            std::memcpy(m_bufferp->data() + m_usedBytes, str, len);
            m_usedBytes += len;
        }
    }
};

class V3OutCFile VL_NOT_FINAL : public V3OutFile {
    int m_guard = false;  // Created header guard
    int m_private;  // 1 = Most recently emitted private:, 2 = public:
public:
    explicit V3OutCFile(const string& filename,
                        V3OutFormatter::Language lang = V3OutFormatter::LA_C)
        : V3OutFile{filename, lang} {
        resetPrivate();
    }
    ~V3OutCFile() override { statRecordWritten(); }
    virtual void putsHeader() { puts("// Verilated -*- C++ -*-\n"); }
    virtual void putsIntTopInclude() { putsForceIncs(); }
    virtual void putsGuard();
    virtual void putsEndGuard() {
        if (m_guard) puts("\n#endif  // guard\n");
    }
    // Print out public/privates
    void resetPrivate() { m_private = 0; }
    void putsPrivate(bool setPrivate) {
        if (setPrivate && m_private != 1) {
            puts("private:\n");
            m_private = 1;
        } else if (!setPrivate && m_private != 2) {
            puts("public:\n");
            m_private = 2;
        }
    }
};

class V3OutJsonFile final : public V3OutFile {
    // CONSTANTS
    static constexpr const char* INDENT = "    ";  // Single indent (4, per JSON std)

    // MEMBERS
private:
    std::stack<char> m_scope;  // Stack of ']' and '}'  to close currently open scopes
    std::string m_prefix;  // Prefix emitted before each line in current scope
    bool m_empty = true;  // Current scope is empty, no comma later

public:
    explicit V3OutJsonFile(const string& filename)
        : V3OutFile{filename, V3OutFormatter::LA_JSON} {
        begin();
    }
    ~V3OutJsonFile() override {
        end();
        puts("\n");
    }
    virtual void putsHeader() {}
    // cppcheck-suppress duplInheritedMember
    void puts(const char* strg) { putsNoTracking(strg); }
    // cppcheck-suppress duplInheritedMember
    void puts(const string& strg) { putsNoTracking(strg); }

    // METHODS
    V3OutJsonFile& begin(const std::string& name, char type = '{') {
        comma();
        puts(m_prefix + "\"" + name + "\": " + type + "\n");
        m_prefix += INDENT;
        m_scope.push(type == '{' ? '}' : ']');
        return *this;
    }
    V3OutJsonFile& begin(char type = '{') {
        comma();
        puts(m_prefix + type + "\n");
        m_prefix += INDENT;
        m_scope.push(type == '{' ? '}' : ']');
        return *this;
    }

    // Put a named value
    V3OutJsonFile& put(const std::string& name, const char* valuep) {
        return putNamed(name, std::string{valuep}, true);
    }
    V3OutJsonFile& put(const std::string& name, const std::string& value) {
        return putNamed(name, value, true);
    }
    V3OutJsonFile& put(const std::string& name, bool value) {
        return putNamed(name, value ? "true" : "false", false);
    }
    V3OutJsonFile& put(const std::string& name, int value) {
        return putNamed(name, std::to_string(value), false);
    }

    // Put unnamed value
    V3OutJsonFile& put(const std::string& value) { return putNamed("", value, true); }
    V3OutJsonFile& put(bool value) { return putNamed("", value ? "true" : "false", false); }
    V3OutJsonFile& put(int value) { return putNamed("", std::to_string(value), false); }

    V3OutJsonFile& putList(const std::string& name, const std::vector<std::string>& list) {
        if (list.empty()) return *this;
        begin(name, '[');
        for (const std::string& str : list) put(str);
        return end();
    }

    V3OutJsonFile& end() {
        UASSERT(m_prefix.length() >= strlen(INDENT), "prefix underflow");
        m_prefix.erase(m_prefix.end() - strlen(INDENT), m_prefix.end());
        UASSERT(!m_scope.empty(), "end() without begin()");
        puts("\n" + m_prefix + m_scope.top());
        m_scope.pop();
        return *this;
    }

    V3OutJsonFile& operator+=(V3OutJsonFile& /*cursor*/) {
        // Meaningless syntax sugar, at least for now
        return *this;
    }

private:
    void comma() {
        if (!m_empty) puts(",\n");
        m_empty = true;
    }
    V3OutJsonFile& putNamed(const std::string& name, const std::string& value, bool quoted) {
        comma();
        const string valueQ
            = quoted ? "\""s + V3OutFormatter::quoteNameControls(value) + "\"" : value;
        if (name.empty()) {
            puts(m_prefix + valueQ);
        } else {
            puts(m_prefix + "\"" + name + "\": " + valueQ);
        }
        m_empty = false;
        return *this;
    }
};

class V3OutMkFile final : public V3OutFile {
public:
    explicit V3OutMkFile(const string& filename)
        : V3OutFile{filename, V3OutFormatter::LA_MK} {}
    ~V3OutMkFile() override = default;
    virtual void putsHeader() { puts("# Verilated -*- Makefile -*-\n"); }
    // No automatic indentation yet.
    // cppcheck-suppress duplInheritedMember
    void puts(const char* strg) { putsNoTracking(strg); }
    // cppcheck-suppress duplInheritedMember
    void puts(const string& strg) { putsNoTracking(strg); }
    // Put VARIABLE = VALUE
    void putSet(const string& var, const string& value) {
        puts(VString::dot(var + " =", " ", value) + "\n");
    }
    // Put VARIABLE ?= VALUE
    void putSetQ(const string& var, const string& value) {
        puts(VString::dot(var + " ?=", " ", value) + "\n");
    }
};

class V3OutVFile final : public V3OutCFile {
public:
    explicit V3OutVFile(const string& filename)
        : V3OutCFile{filename, V3OutFormatter::LA_VERILOG} {}
    ~V3OutVFile() override = default;
    void putsHeader() override { puts("// Verilated -*- Verilog -*-\n"); }
};

class V3OutXmlFile final : public V3OutFile {
public:
    explicit V3OutXmlFile(const string& filename)
        : V3OutFile{filename, V3OutFormatter::LA_XML} {
        blockIndent(2);
    }
    ~V3OutXmlFile() override = default;
    virtual void putsHeader() { puts("<?xml version=\"1.0\" ?>\n"); }
};

//============================================================================
// V3OutStream: A class for printing formatted code to any std::ostream

class V3OutStream VL_NOT_FINAL : public V3OutFormatter {
    // MEMBERS
    std::ostream& m_ostream;

    VL_UNCOPYABLE(V3OutStream);
    VL_UNMOVABLE(V3OutStream);

public:
    V3OutStream(std::ostream& ostream, V3OutFormatter::Language lang);
    ~V3OutStream() override = default;

    void putcOutput(char chr) override { m_ostream << chr; };
    void putsOutput(const char* str) override { m_ostream << str; };
};

//============================================================================
// VIdProtect: Hash identifier names in output files to protect them

class VIdProtectImp;

class VIdProtect final {
public:
    // METHODS
    // Return 'in' only if not protecting (e.g. for emitting a comment)
    static string ifNoProtect(const string& in) VL_MT_SAFE;
    // Rename to a new encoded string (unless earlier passthru'ed)
    static string protect(const string& old) VL_MT_SAFE { return protectIf(old, true); }
    static string protectIf(const string& old, bool doIt = true) VL_MT_SAFE;
    // Rename words to a new encoded string
    static string protectWordsIf(const string& old, bool doIt = true) VL_MT_SAFE;
    // Write map of renames to output file
    static void writeMapFile(const string& filename);
};

#endif  // Guard
