/*
 * Copyright (c) Contributors to the Open 3D Engine Project.
 * For complete copyright and license terms please see the LICENSE at the root of this distribution.
 *
 * SPDX-License-Identifier: Apache-2.0 OR MIT
 *
 */
#pragma once

#include "StdUtils.h"

namespace AZ::ShaderCompiler
{
    //! Describes a preprocessor line directive.
    //! this is not part of the AST, that is why this is not in AzslcUtils.h
    struct LineDirectiveInfo
    {
        size_t m_physicalTokenLine;  //!< line where the preprocessor token appears
        size_t m_forcedLineNumber;   //!< the line number as specified (parsed from syntax input)
        string m_containingFilename;
    };

    //! A helper capable of finding the original line in the source file
    //! for the given flat file that is being compiled.
    //! A preprocessed azsl file is the file generated by MCPP after all #include and macro definitions
    //! have been resolved in a single flat file.
    //! If an error is observed at any given line in the flat file, you can use this interface to find the original file
    //! and line number where the error is coming from.
    class PreprocessorLineDirectiveFinder
    {
    public:
        const LineDirectiveInfo* GetNearestPreprocessorLineDirective(size_t physicalLine) const
        {
            auto it = Infimum(m_lineMap, physicalLine);
            return it == m_lineMap.cend() ? nullptr : &it->second;
        }

        //! The "virtual" line is the #line-directive adjusted line number, as opposed to a physical line (given by tokens)
        size_t GetVirtualLineNumber(const LineDirectiveInfo& lineInfo, size_t physicalLine) const
        {
            //  visual explanation:
            // code editor line │ actual source                                   │ remark
            // ─────────────────┼─────────────────────────────────────────────────┼────────────────────────────
            //                1 │ # line 4 "name"                                 │  ┐
            //                2 │              // this is now line 4, virtually   │  ├  physical distance = 3-1 = 2
            //                3 │ $yntaX error                                    │  ┘
            //
            // the error is on physical line 3, but should be reported as being on line 5 (physical-distance + line## - 1)
            const size_t physicalDistance = physicalLine - lineInfo.m_physicalTokenLine;
            return physicalDistance + lineInfo.m_forcedLineNumber - 1;  // virtual line
        }

        //! More compact API for those who just need the final count adjustment, without access to the LineDirectiveInfo
        size_t GetVirtualLineNumber(size_t physicalLine) const
        {
            const LineDirectiveInfo* found = GetNearestPreprocessorLineDirective(physicalLine);
            return found ? GetVirtualLineNumber(*found, physicalLine)
                         : physicalLine;  // Return unadjusted in case of abscence of a virtual space.
        }

        const string& GetVirtualFileName(size_t physicalLine) const
        {
            const LineDirectiveInfo* found = GetNearestPreprocessorLineDirective(physicalLine);
            return found ? found->m_containingFilename
                         : m_physicalSourceFileName;  // Return unadjusted in case of abscence of a virtual space.
        }

        void PushLineDirective(const LineDirectiveInfo& ldi)
        {
            m_lineMap[ldi.m_physicalTokenLine] = ldi;
        }

        void PushLineDirective(LineDirectiveInfo&& ldi)
        {
            m_lineMap.emplace(ldi.m_physicalTokenLine, ldi);
        }

        map<size_t, LineDirectiveInfo> m_lineMap;
        string                         m_physicalSourceFileName;
    };
}
