// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt
// Project developers.  See the top-level LICENSE file for dates and other
// details.  No copyright assignment is required to contribute to VisIt.

// ************************************************************************* //
//                               avtXmdvWriter.C                             //
// ************************************************************************* //

#include <avtXmdvWriter.h>

#include <vtkCellData.h>
#include <vtkDataArray.h>
#include <vtkDataSet.h>
#include <vtkPointData.h>
#include <vtkVisItUtility.h>

#include <DBOptionsAttributes.h>

#include <avtCallback.h>
#include <avtDatabaseMetaData.h>
#include <avtParallelContext.h>

#include <DebugStream.h>
#include <InvalidVariableException.h>

#include <string>
#include <vector>

using     std::string;
using     std::vector;


// ****************************************************************************
//  Method: avtXmdvWriter constructor
//
//  Programmer: childs -- generated by xml2avt
//  Creation:   Tue Jul 19 17:12:44 PST 2005
// 
//  Modifications:
//    Justin Privitera, Mon 07 Mar 2022 06:04:53 PM PST
//    Added gui option to set output precision.
//
// ****************************************************************************

avtXmdvWriter::avtXmdvWriter(const DBOptionsAttributes *atts)
{
    writeOutCoordinates = atts->GetBool("Export coordinates?");
    outputPrecision = atts->GetInt("Output precision");
    // Get it in the range 1-18
    if (outputPrecision > 18) 
    {
        outputPrecision = 18;
    }
    else if (outputPrecision < 1) 
    {
        outputPrecision = 1;
    }
}

// ****************************************************************************
//  Method: avtXmdvWriter::OpenFile
//
//  Purpose:
//      Does no actual work.  Just records the stem name for the files.
//
//  Programmer: childs -- generated by xml2avt
//  Creation:   Tue Jul 19 17:12:44 PST 2005
//
//  Modifications:
//    Jeremy Meredith, Tue Mar 27 15:10:21 EDT 2007
//    Added nblocks to this function and save it so we don't have to 
//    trust the meta data.
//
// ****************************************************************************

void
avtXmdvWriter::OpenFile(const string &stemname, int nb)
{
    stem = stemname;
    nblocks = nb;
}


// ****************************************************************************
//  Method: avtXmdvWriter::WriteHeaders
//
//  Purpose:
//      Writes out a VisIt file to tie the Xmdv files together.
//
//  Programmer: childs -- generated by xml2avt
//  Creation:   Tue Jul 19 17:12:44 PST 2005
//
//  Modifications:
//
//    Hank Childs, Tue Sep 27 08:39:14 PDT 2005
//    Added support for vectors.
//
//    Hank Childs, Wed Jan  3 14:21:28 PST 2007
//    Fix problem with parallel initialization.
//
//    Jeremy Meredith, Tue Mar 27 17:22:40 EDT 2007
//    Use the saved nblocks so we don't have to trust the meta data.
//
//    Kathleen Bonnell, Mon Apr 16 11:38:34 PDT 2007 
//    Since centering of expression cannot be determined here, put off setting
//    of varsAreNodal flag till WriteChunk.
//
// ****************************************************************************

void
avtXmdvWriter::WriteHeaders(const avtDatabaseMetaData *md,
                            const vector<string> &s,
                            const vector<string> &v,
                            const vector<string> &materials)
{
    scalars = s;
    vectors = v;
    if (materials.size() != 0)
        avtCallback::IssueWarning("Materials ignored by Xmdv writer");

    bool haveCentering = false;
    varCentering = AVT_UNKNOWN_CENT;
    for (size_t i = 0 ; i < s.size() ; i++)
    {
        if (md->GetScalar(s[i]) != NULL)
        {
            if (!haveCentering)
            {
                varCentering = md->GetScalar(s[i])->centering;
                haveCentering = true;
            }
            else if (md->GetScalar(s[i])->centering != varCentering)
            {
                EXCEPTION1(VisItException, "All variables must have the "
                    "same centering.  You can re-center variables by using"
                    " the \"recenter\" expression.");
            }
        }
        else
            debug1 << "Cannot check centering of " << s[i].c_str() 
                   << ", probably an expression.  Hoping for the best."<< endl;
    }
    for (size_t i = 0 ; i < v.size() ; i++)
    {
        if (md->GetVector(v[i]) != NULL)
        {
            if (!haveCentering)
            {
                varCentering = md->GetVector(v[i])->centering;
                haveCentering = true;
            }
            else if (md->GetVector(v[i])->centering != varCentering)
            {
                EXCEPTION1(VisItException, "All variables must have the "
                    "same centering.  You can re-center variables by using"
                    " the \"recenter\" expression.");
            }
        }
        else
            debug1 << "Cannot check centering of " << v[i].c_str() 
                   << ", probably an expression.  Hoping for the best."<< endl;
    }

    if (nblocks > 1)
    {
        onlyOneBlock = false;
    }
    else
    {
        onlyOneBlock = true;
    }
}


// ****************************************************************************
//  Method: avtXmdvWriter::WriteChunk
//
//  Purpose:
//      This writes out one chunk of an avtDataset.
//
//  Programmer: childs -- generated by xml2avt
//  Creation:   Tue Jul 19 17:12:44 PST 2005
//
//  Modifications:
//    Kathleen Bonnell, Mon Apr 16 11:26:39 PDT 2007
//    Retest for varCentering here if necessary.  Throw excepton if we cannot
//    determine centering.
//
//    Brad Whitlock, Fri Jul 24 11:31:23 PDT 2009
//    I made the columns write values with more precision.
// 
//    Justin Privitera, Mon 07 Mar 2022 06:02:13 PM PST
//    I allowed the user to specify the output precision they wished to write 
//    with.
//
// ****************************************************************************

void
avtXmdvWriter::WriteChunk(vtkDataSet *ds, int chunk)
{
    size_t npts = ds->GetNumberOfPoints();
    size_t ncells = ds->GetNumberOfCells();
    if (varCentering == AVT_UNKNOWN_CENT) 
    {
        if (scalars.size() > 0)
        {
            if (ds->GetPointData()->GetArray(scalars[0].c_str()) != NULL)
                varCentering = AVT_NODECENT;
            else if (ds->GetCellData()->GetArray(scalars[0].c_str()) != NULL)
                varCentering = AVT_ZONECENT;
        }
        else if (vectors.size() > 0)
        {
            if (ds->GetPointData()->GetArray(vectors[0].c_str()) != NULL)
                varCentering = AVT_NODECENT;
            else if (ds->GetCellData()->GetArray(vectors[0].c_str()) != NULL)
                varCentering = AVT_ZONECENT;
        }
    }

    if (varCentering == AVT_UNKNOWN_CENT)
    {
        EXCEPTION1(VisItException, "Unable to determine Variable centering."
                    "  It could be an invalid variable.");
    }

    varsAreNodal = varCentering == AVT_NODECENT;
    size_t nvals = (varsAreNodal ? npts : ncells);

    if (nvals == 0)
        return;

    char name[1024];
    if (onlyOneBlock)
        snprintf(name, 1024, "%s.okc", stem.c_str());
    else
        snprintf(name, 1024, "%s.%03d.okc", stem.c_str(), chunk);

    ofstream ofile(name);
    if (ofile.fail())
        EXCEPTION0(ImproperUseException);

    size_t nScalars = scalars.size() + 3*vectors.size();
    if (writeOutCoordinates)
        nScalars += 3;
    ofile << nScalars << " " << nvals << " 1" << endl;
    if (writeOutCoordinates)
        ofile << "x\ny\nz\n";
    for (size_t i = 0 ; i < scalars.size() ; i++)
    {
        ofile << scalars[i].c_str() << endl;
    }
    for (size_t i = 0 ; i < vectors.size() ; i++)
    {
        ofile << vectors[i].c_str() << "[0]" << endl;
        ofile << vectors[i].c_str() << "[1]" << endl;
        ofile << vectors[i].c_str() << "[2]" << endl;
    }

    vtkDataArray **arrays_sca = new vtkDataArray*[scalars.size()];
    vtkDataArray **arrays_vec = new vtkDataArray*[vectors.size()];
    for (size_t i = 0 ; i < scalars.size() ; i++)
    {
        arrays_sca[i] = (varsAreNodal 
                           ? ds->GetPointData()->GetArray(scalars[i].c_str())
                           : ds->GetCellData()->GetArray(scalars[i].c_str()));
        if (arrays_sca[i] == NULL)
        {
            delete [] arrays_sca;
            delete [] arrays_vec;
            EXCEPTION1(InvalidVariableException, scalars[i].c_str());
        }
    }
    for (size_t i = 0 ; i < vectors.size() ; i++)
    {
        arrays_vec[i] = (varsAreNodal 
                           ? ds->GetPointData()->GetArray(vectors[i].c_str())
                           : ds->GetCellData()->GetArray(vectors[i].c_str()));
        if (arrays_vec[i] == NULL)
        {
            delete [] arrays_sca;
            delete [] arrays_vec;
            EXCEPTION1(InvalidVariableException, vectors[i].c_str());
        }
    }

    if (writeOutCoordinates)
    {
        double bounds[6];
        ds->GetBounds(bounds);
        ofile << bounds[0] << "\t" << bounds[1] << "\t10" << endl;
        ofile << bounds[2] << "\t" << bounds[3] << "\t10" << endl;
        ofile << bounds[4] << "\t" << bounds[5] << "\t10" << endl;
    }

    for (size_t i = 0 ; i < scalars.size() ; i++)
    {
        float min = arrays_sca[i]->GetTuple1(0);
        float max = arrays_sca[i]->GetTuple1(0);
        for (size_t j = 0 ; j < nvals ; j++)
        {
            float v = arrays_sca[i]->GetTuple1(j);
            min = (min < v ? min : v);
            max = (max > v ? max : v);
        }
        ofile << min << "\t" << max << "\t10" << endl;
    }
    for (size_t i = 0 ; i < vectors.size() ; i++)
    {
        double vec[3];
        arrays_vec[i]->GetTuple(i, vec);
        float minI = vec[0];
        float maxI = vec[0];
        float minJ = vec[1];
        float maxJ = vec[1];
        float minK = vec[2];
        float maxK = vec[2];
        for (size_t j = 0 ; j < nvals ; j++)
        {
            arrays_vec[i]->GetTuple(j, vec);
            minI = (minI < vec[0] ? minI : vec[0]);
            maxI = (maxI > vec[0] ? maxI : vec[0]);
            minJ = (minJ < vec[1] ? minJ : vec[1]);
            maxJ = (maxJ > vec[1] ? maxJ : vec[1]);
            minK = (minK < vec[2] ? minK : vec[2]);
            maxK = (maxK > vec[2] ? maxK : vec[2]);
        }
        ofile << minI << "\t" << maxI << "\t10" << endl;
        ofile << minJ << "\t" << maxJ << "\t10" << endl;
        ofile << minK << "\t" << maxK << "\t10" << endl;
    }

    ofile << std::scientific << std::setprecision(outputPrecision);
    for (size_t j = 0 ; j < nvals ; j++)
    {
        if (writeOutCoordinates)
        {
            double pt[3];
            if (varsAreNodal)
                ds->GetPoint(j, pt);
            else
                vtkVisItUtility::GetCellCenter(ds->GetCell(j), pt);
            ofile << pt[0] << "\t" << pt[1] << "\t" << pt[2] << "\t";
        }
        for (size_t i = 0 ; i < scalars.size() ; i++)
        {
            float v = arrays_sca[i]->GetTuple1(j);
            ofile << v;
            bool needTab = false;
            if (vectors.size() != 0)
                needTab = true;
            if (i != (scalars.size()-1))
                needTab = true;
            if (needTab)
                ofile << "\t";
        }
        for (size_t i = 0 ; i < vectors.size() ; i++)
        {
            double vec[3];
            arrays_vec[i]->GetTuple(j, vec);
            ofile << vec[0] << "\t" << vec[1] << "\t" << vec[2];
            if (i != (vectors.size()-1))
                ofile << "\t";
        }
        ofile << endl;
    }
    delete [] arrays_sca;
    delete [] arrays_vec;
}


// ****************************************************************************
//  Method: avtXmdvWriter::CloseFile
//
//  Purpose:
//      Closes the file.  This does nothing in this case.
//
//  Programmer: childs -- generated by xml2avt
//  Creation:   Tue Jul 19 17:12:44 PST 2005
//
// ****************************************************************************

void
avtXmdvWriter::CloseFile(void)
{
    // CLOSE FILES
}

void
avtXmdvWriter::WriteRootFile()
{
    if (nblocks > 1 && writeContext.Rank() == 0)
    {
        char filename[1024];
        sprintf(filename, "%s.visit", stem.c_str());
        ofstream ofile(filename);
        ofile << "!NBLOCKS " << nblocks << endl;
        for (int i = 0 ; i < nblocks ; i++)
        {
            char chunkname[1024];
            sprintf(chunkname, "%s.%03d.okc", stem.c_str(), i);
            ofile << chunkname << endl;
        }
    }
}
