/*
 * This file is part of the GROMACS molecular simulation package.
 *
 * Copyright 2021- The GROMACS Authors
 * and the project initiators Erik Lindahl, Berk Hess and David van der Spoel.
 * Consult the AUTHORS/COPYING files and https://www.gromacs.org for details.
 *
 * GROMACS is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * GROMACS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with GROMACS; if not, see
 * https://www.gnu.org/licenses, or write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
 *
 * If you want to redistribute modifications to GROMACS, please
 * consider that scientific software is very special. Version
 * control is crucial - bugs must be traceable. We will be happy to
 * consider code for inclusion in the official distribution, but
 * derived work must not be called official GROMACS. Details are found
 * in the README & COPYING files - if they are missing, get the
 * official version at https://www.gromacs.org.
 *
 * To help us fund GROMACS development, we humbly ask that you cite
 * the research papers on the package. Check out https://www.gromacs.org.
 */
/*! \internal \file
 * \brief
 * Tests for gmx chi.
 *
 * \author Mark Abraham <mark.j.abraham@gmail.com>
 */

#include "gmxpre.h"

#include <cstring>

#include <initializer_list>
#include <map>
#include <string>
#include <vector>

#include <gtest/gtest.h>

#include "gromacs/gmxana/gmx_ana.h"
#include "gromacs/utility/stringutil.h"

#include "testutils/cmdlinetest.h"
#include "testutils/testasserts.h"
#include "testutils/testfilemanager.h"
#include "testutils/textblockmatchers.h"
#include "testutils/xvgtest.h"

namespace gmx::test
{
namespace
{

class GmxChiTest : public CommandLineTestBase
{
public:
    GmxChiTest()
    {
        setInputFile("-f", "lysozyme-traj.gro");
        setInputFile("-s", "lysozyme.pdb");
        setInputFileContents("-ss", "ssdump.dat", "10 .EEHHE.HHE");

        // Ensure we don't match the per-residue lines of J-coupling
        // data, whose standard deviations are computed in a way that
        // is sensitive to the build configuration.
        std::vector<std::string> logLinesToSkip{ "^[A-Z]{3,4}[0-9]+.*" };
        setOutputFile("-g", "chi.log", FilteringExactTextMatch(logLinesToSkip, false, true));

        setOutputFile("-o", "order.xvg", XvgMatch());
        setOutputFile("-jc", "Jcoupling.xvg", XvgMatch());
        setOutputFile("-ot", "dihtrans.xvg", XvgMatch());
        setOutputFile("-oh", "trhisto.xvg", XvgMatch());
        setOutputFile("-rt", "restrans.xvg", XvgMatch());
        setOutputFile("-cp", "chiprodhisto.xvg", XvgMatch());

        commandLine().addOption("-phi");
        commandLine().addOption("-psi");
        commandLine().addOption("-omega");
        commandLine().addOption("-chi_prod");
        commandLine().addOption("-maxchi", "6");

        // Test the dihedral-related output files that are generated by
        // gmx chi from the input data.
    }

    void setOutputFiles(const std::map<const char*, std::vector<const char*>>& residueNamesWithDihedrals)
    {
        for (const auto& [dihedralName, residueNames] : residueNamesWithDihedrals)
        {
            for (const auto& residueName : residueNames)
            {
                ASSERT_GE(std::strlen(residueName), 3);
                const char residueType[4] = { residueName[0], residueName[1], residueName[2], '\0' };
                const auto mainHistogramOutputFileName =
                        formatString("histo-%s%s.xvg", dihedralName, residueType);
                setOutputFileWithGeneratedName(
                        mainHistogramOutputFileName, mainHistogramOutputFileName, XvgMatch());
                for (const char* secondaryStructureName : { "sheet", "helix", "coil" })
                {
                    const auto histogramOutputFileName = formatString(
                            "histo-%s%s-%s.xvg", dihedralName, residueType, secondaryStructureName);
                    setOutputFileWithGeneratedName(
                            histogramOutputFileName, histogramOutputFileName, XvgMatch());
                }
                if (std::strstr(dihedralName, "chi") != nullptr)
                {
                    const auto chiHistogramOutputFileName =
                            formatString("histo-%s%s.xvg", dihedralName, residueType);
                    setOutputFileWithGeneratedName(
                            chiHistogramOutputFileName, chiHistogramOutputFileName, XvgMatch());
                    for (const char* secondaryStructureName : { "sheet", "helix", "coil" })
                    {
                        const auto histogramOutputFileName = formatString(
                                "histo-%s%s-%s.xvg", dihedralName, residueType, secondaryStructureName);
                        setOutputFileWithGeneratedName(
                                histogramOutputFileName, histogramOutputFileName, XvgMatch());
                    }
                }
                const auto outputFileName = formatString("%s%s.xvg", dihedralName, residueName);
                setOutputFileWithGeneratedName(
                        outputFileName,
                        outputFileName,
                        XvgMatch().tolerance(relativeToleranceAsFloatingPoint(90, 1e-4)));
                if (std::strstr(dihedralName, "chi") != nullptr)
                {
                    const auto chiProductOutputFileName = formatString("chiproduct%s.xvg", residueName);
                    setOutputFileWithGeneratedName(
                            chiProductOutputFileName,
                            chiProductOutputFileName,
                            XvgMatch().tolerance(relativeToleranceAsFloatingPoint(-50, 1e-4)));
                    const auto histogramOutputFileName = formatString("histo-chiprod%s.xvg", residueName);
                    setOutputFileWithGeneratedName(
                            histogramOutputFileName, histogramOutputFileName, XvgMatch());
                }
            }
        }
    }
};

// Note that using '-all' adds extra output while changing and
// removing none, so we test with '-all' for simplicity and reducing
// refdata file size.
TEST_F(GmxChiTest, gmxchiWorksWithAll)
{
    commandLine().addOption("-all");

    // Test the extra dihedral-related output files that are generated
    // by gmx chi from the input data with -all.

    std::map<const char*, std::vector<const char*>> residueNamesWithDihedrals{
        // First the backbone dihedrals. All residues have phi and
        // psi dihedrals.
        { "phi",
          { "LYS1", "VAL2", "PHE3", "GLY4", "ARG5", "CYS6", "GLU7", "LEU8", "ALA9", "ALA10" } },
        { "psi",
          { "LYS1", "VAL2", "PHE3", "GLY4", "ARG5", "CYS6", "GLU7", "LEU8", "ALA9", "ALA10" } },
        // There is no omega dihedral angle into the first
        // residue, as there is no peptide bond before it.
        { "omega", { "VAL2", "PHE3", "GLY4", "ARG5", "CYS6", "GLU7", "LEU8", "ALA9", "ALA10" } },
        // Then the (off-backbone) chi dihedrals
        // All residues in lysozyme.pdb have at least one chi
        // dihedral, except GLY4 of course.
        { "chi1", { "LYS1", "VAL2", "PHE3", "ARG5", "CYS6", "GLU7", "LEU8" } },
        // Fewer residue types have a second chi dihedral
        { "chi2", { "LYS1", "PHE3", "ARG5", "GLU7", "LEU8" } },
        // Fewer residue types have a third chi dihedral
        { "chi3", { "LYS1", "ARG5", "GLU7" } },
        // Fewer residue types have a fourth chi dihedral
        { "chi4", { "LYS1", "ARG5" } }
    };
    setOutputFiles(residueNamesWithDihedrals);

    ASSERT_EQ(0, gmx_chi(commandLine().argc(), commandLine().argv()));
    checkOutputFiles();
}

TEST_F(GmxChiTest, gmxchiWorksWithr0AndrN)
{
    commandLine().addOption("-all");
    commandLine().addOption("-r0", "2");
    commandLine().addOption("-rN", "6");

    // Test the dihedral-related output files that are generated
    // by gmx chi from the input data with -r0, -rN, and -all.

    std::map<const char*, std::vector<const char*>> residueNamesWithDihedrals{
        // First the backbone dihedrals. All residues have phi and
        // psi dihedrals.
        { "phi", { "VAL2", "PHE3", "GLY4", "ARG5", "CYS6" } },
        { "psi", { "VAL2", "PHE3", "GLY4", "ARG5", "CYS6" } },
        // There is no omega dihedral angle into the first
        // residue, as there is no peptide bond before it.
        { "omega", { "VAL2", "PHE3", "GLY4", "ARG5", "CYS6" } },
        // Then the (off-backbone) chi dihedrals
        // All residues in lysozyme.pdb have at least one chi
        // dihedral, except GLY4 of course.
        { "chi1", { "VAL2", "PHE3", "ARG5", "CYS6" } },
        // Fewer residue types have a second chi dihedral
        { "chi2", { "PHE3", "ARG5" } },
        // Fewer residue types have a third chi dihedral
        { "chi3", { "ARG5" } },
        // Fewer residue types have a fourth chi dihedral
        { "chi4", { "ARG5" } }
    };
    setOutputFiles(residueNamesWithDihedrals);

    ASSERT_EQ(0, gmx_chi(commandLine().argc(), commandLine().argv()));
    checkOutputFiles();
}

} // namespace
} // namespace gmx::test
