/*
** (c) 1996-2000 The Regents of the University of California (through
** E.O. Lawrence Berkeley National Laboratory), subject to approval by
** the U.S. Department of Energy.  Your use of this software is under
** license -- the license agreement is attached and included in the
** directory as license.txt or you may contact Berkeley Lab's Technology
** Transfer Department at TTD@lbl.gov.  NOTICE OF U.S. GOVERNMENT RIGHTS.
** The Software was developed under funding from the U.S. Government
** which consequently retains certain rights as follows: the
** U.S. Government has been granted for itself and others acting on its
** behalf a paid-up, nonexclusive, irrevocable, worldwide license in the
** Software to reproduce, prepare derivative works, and perform publicly
** and display publicly.  Beginning five (5) years after the date
** permission to assert copyright is obtained from the U.S. Department of
** Energy, and subject to any subsequent five (5) year renewals, the
** U.S. Government is granted for itself and others acting on its behalf
** a paid-up, nonexclusive, irrevocable, worldwide license in the
** Software to reproduce, prepare derivative works, distribute copies to
** the public, perform publicly and display publicly, and to permit
** others to do so.
*/

//BL_COPYRIGHT_NOTICE

#include <new>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <list>
#include <map>

using std::cout;
using std::endl;

#ifndef WIN32
#include <unistd.h>
#endif

#ifdef USE_TEC_BIN_IO
#include "TECIO.h"
#endif

#include "ParmParse.H"
#include "ParallelDescriptor.H"
#include "DataServices.H"
#include "Utility.H"
#include "FArrayBox.H"

struct Node
{
    enum typeEnum{INIT=0, COVERED=1, VALID=2};
    Node()
        : level(-1), iv(IntVect(D_DECL(-1,-1,-1))), type(Node::INIT) {}
    Node(const IntVect& idx, int lev, typeEnum typ = INIT)
        : level(lev), iv(idx), type(typ) {}
    inline bool operator< (const Node& rhs) const
        {
            if (level < rhs.level) return true;
            if ((level == rhs.level) && iv.lexLT(rhs.iv)) return true;
            return false;
        }
    inline bool operator!= (const Node& rhs) const
        {
            return ((*this) < rhs || rhs < (*this));
        }
    IntVect iv;
    int level;
    typeEnum type;
};

std::ostream& operator<< (std::ostream&  os, const Node& node)
{
    os << "Node: IntVect=" << node.iv << ", level=" << node.level << ", type=";
    if (node.type==Node::INIT)
        os << "INIT";
    else if (node.type==Node::COVERED)
        os << "COVERED";
    else
        os << "VALID";
    if (os.fail())
        BoxLib::Error("operator<<(ostream&,Node&) failed");
    return os;
}

struct Element
{
#if (BL_SPACEDIM==2)
#define MYLEN 4
    Element(const Node& a, const Node& b, const Node& c, const Node& d)
        {  n[0]=&a; n[1]=&b; n[2]=&c; n[3]=&d; }
#else
#define MYLEN 8
    Element(const Node& a, const Node& b, const Node& c, const Node& d,
            const Node& e, const Node& f, const Node& g, const Node& h)
        {  n[0]=&a; n[1]=&b; n[2]=&c; n[3]=&d; n[4]=&e; n[5]=&f; n[6]=&g; n[7]=&h; }
#endif
    const Node* n[MYLEN];
    inline bool operator< (const Element& rhs) const
        {
            for (int i=0; i<MYLEN; ++i)
                if (*n[i] != *rhs.n[i])
                    return *n[i] < *rhs.n[i];
            return false;
        }
};

static
void 
print_usage (int,
             char* argv[])
{
    std::cerr << "usage:\n";
    std::cerr << argv[0] << " infile [options] \n\tOptions:\n";
    std::cerr << "\t     comps=int comp list [overrides sComp/nComp]\n";
    std::cerr << "\t     sComp=start comp[DEF->0]\n";
    std::cerr << "\t     nComp=number of comps[DEF->all]\n";
    std::cerr << "\t     box=int list of LL+UR of subbox (lev-0 coords) [DEF->all]>\n";
    exit(1);
}

class FABdata
{
public:
    FABdata(size_t i) {fab.resize(Box(IntVect::TheZeroVector(),IntVect(i,0,0)),1);}
    Real& operator[](size_t i) {return fab(IntVect(D_DECL(i,0,0)));}
    FArrayBox fab;
};

void
main (int   argc,
      char* argv[])
{
    BoxLib::Initialize(argc,argv);

    if (argc < 2)
        print_usage(argc,argv);

    ParmParse pp;

    if (pp.contains("help"))
        print_usage(argc,argv);

    if (pp.contains("verbose"))
        AmrData::SetVerbose(true);

    std::string infile; pp.get("infile",infile);
    std::string outfile_DEF;

    std::string outType = "flt"; pp.query("outType",outType);
    if (outType=="flt")
    {
        outfile_DEF = infile+".flt";
    }
    else if (outType=="tec")
    {
#ifdef USE_TEC_BIN_IO
        outfile_DEF = infile+".plt";
#else
        outfile_DEF = infile+".dat";
#endif
    }
    else
    {
        print_usage(argc,argv);
    }

    std::string outfile(outfile_DEF); pp.query("outfile",outfile);
    DataServices::SetBatchMode();
    FileType fileType(NEWPLT);
    DataServices dataServices(infile, fileType);

    if (!dataServices.AmrDataOk())
        DataServices::Dispatch(DataServices::ExitRequest, NULL);

    AmrData& amrData = dataServices.AmrDataRef();

    const Array<std::string>& names = amrData.PlotVarNames();

    Array<int> comps;
    if (int nc = pp.countval("comps"))
    {
        comps.resize(nc);
        pp.getarr("comps",comps,0,nc);
    }
    else
    {
        int sComp = 0;
        pp.query("sComp",sComp);
        int nComp = amrData.NComp();
        pp.query("nComp",nComp);
        BL_ASSERT(sComp+nComp <= amrData.NComp());
        comps.resize(nComp);
        for (int i=0; i<nComp; ++i)
            comps[i] = sComp + i;
    }

    Box subbox;
    if (int nx=pp.countval("box"))
    {
        Array<int> barr;
        pp.getarr("box",barr,0,nx);
        int d=BL_SPACEDIM;
        BL_ASSERT(barr.size()==2*d);
        subbox=Box(IntVect(D_DECL(barr[0],barr[1],barr[2])),
                   IntVect(D_DECL(barr[d],barr[d+1],barr[d+2])));

    } else {

        subbox = amrData.ProbDomain()[0];
    }

    int Nlev = amrData.FinestLevel() + 1;
    Array<BoxArray> gridArray(Nlev);
    Array<Box> subboxArray(Nlev);

    for (int lev=0; lev<Nlev; ++lev)
    {
        subboxArray[lev]
            = (lev==0 ? subbox
               : Box(subboxArray[lev-1]).refine(amrData.RefRatio()[lev-1]));
        gridArray[lev] = BoxLib::intersect(amrData.boxArray(lev), subboxArray[lev]);
        if (!gridArray[lev].size())
        {
            Nlev = lev;
            gridArray.resize(Nlev);
            subboxArray.resize(Nlev);
        }
    }

    const int nGrow = 1;
    typedef BaseFab<Node> NodeFab;
    typedef FabArray<NodeFab> MultiNodeFab; 
    PArray<MultiNodeFab> nodes(Nlev,PArrayManage);

    std::cerr << "Before nodes allocated" << endl;
    for (int lev=0; lev<Nlev; ++lev)
        nodes.set(lev,new MultiNodeFab(gridArray[lev],1,nGrow));
    std::cerr << "After nodes allocated" << endl;

    int cnt = 0;
    typedef std::map<Node,int> NodeMap;
    NodeMap nodeMap;
    for (int lev=0; lev<Nlev; ++lev)
    {
        const int ref = (lev==0 ? 1 : amrData.RefRatio()[lev-1]);

        for (MFIter fai(nodes[lev]); fai.isValid(); ++fai)
        {
            const Box& box = nodes[lev].box(fai.index());
            NodeFab& ifab = nodes[lev][fai];
            for (IntVect iv=box.smallEnd(); iv<=box.bigEnd(); box.next(iv))
                ifab(iv,0) = Node(iv,lev,Node::VALID);

            if (lev != 0)
            {
                // Set Nodes in grow cells over coarse cells
                const Box fBox =
                    BoxLib::grow(fai.validbox(),ref) & subboxArray[lev];
                BoxList fBL = BoxLib::boxDiff(fBox,fai.validbox()); // Remove valid

                for (BoxList::iterator bli=fBL.begin(); bli!=fBL.end(); ++bli)
                {
                    const Box& dstBox = *bli;
                    const Box& srcBox = BoxLib::coarsen(dstBox,ref);

                    NodeFab dst(dstBox,1);
                    for (IntVect iv(srcBox.smallEnd());
                         iv<=srcBox.bigEnd();
                         srcBox.next(iv))
                    {
                        const IntVect baseIV = ref*iv;

                        const Box rangeBox = Box(IntVect::TheZeroVector(),
                                                 (ref-1)*IntVect::TheUnitVector());
                        
                        for (IntVect ivt(rangeBox.smallEnd());ivt<=rangeBox.bigEnd();rangeBox.next(ivt))
                            dst(baseIV + ivt,0) = Node(iv,lev-1,Node::VALID);
                    }
                    const Box ovlp = dstBox & ifab.box();
                    BL_ASSERT(ovlp.ok());
                    ifab.copy(dst,ovlp,0,ovlp,0,1);
                }
            }

            // Set fine-fine cells
            BoxArray ff = BoxLib::intersect(BoxArray(BoxLib::boxDiff(ifab.box(),fai.validbox())),
                                            gridArray[lev]);
            for (int i=0; i<ff.size(); ++i)
                for (IntVect iv(ff[i].smallEnd()); iv<=ff[i].bigEnd(); ff[i].next(iv))
                    ifab(iv,0) = Node(iv,lev,Node::VALID);

            // Block out cells covered by finer grid
            if (lev < Nlev - 1)
            {
                const BoxArray coarsendFineBoxes =
                    BoxArray(gridArray[lev+1]).coarsen(amrData.RefRatio()[lev]);
                
                for (MFIter fai(nodes[lev]); fai.isValid(); ++fai)
                {
                    for (int i=0; i<coarsendFineBoxes.size(); ++i)
                    {
                        const Box ovlp = ifab.box() & coarsendFineBoxes[i];
                        if (ovlp.ok())
                        {
                            for (IntVect iv=ovlp.smallEnd();
                                 iv<=ovlp.bigEnd(); ovlp.next(iv))
                                ifab(iv,0) = Node(iv,lev,Node::COVERED);
                        }
                    }
                }
            }

            // Add the unique nodes from this level to the list
            for (IntVect iv(box.smallEnd()); iv<=box.bigEnd(); box.next(iv))
                if (ifab(iv,0).type == Node::VALID)
                    nodeMap[ifab(iv,0)] = cnt++;
        }
    }

    std::cerr << "After nodeMap built, size=" << nodeMap.size() << endl;

    typedef std::set<Element> EltSet;
    EltSet elements;
    for (int lev=0; lev<Nlev; ++lev)
    {
        for (MFIter fai(nodes[lev]); fai.isValid(); ++fai)
        {
            Box box = fai.validbox();
            for (int dir=0; dir<BL_SPACEDIM; ++dir)
                box.growLo(dir,1);

            box &= subboxArray[lev];

            NodeFab& ifab = nodes[lev][fai];
                        
            for (IntVect iv(box.smallEnd()); iv<=box.bigEnd(); box.next(iv))
            {
#if (BL_SPACEDIM == 2)
                const Node& n1 = ifab(iv,0);
                const Node& n2 = ifab(IntVect(iv).shift(BoxLib::BASISV(0)),0);
                const Node& n3 = ifab(IntVect(iv).shift(IntVect::TheUnitVector()),0);
                const Node& n4 = ifab(IntVect(iv).shift(BoxLib::BASISV(1)),0);
                if (n1.type==Node::VALID && n2.type==Node::VALID &&
                    n3.type==Node::VALID && n4.type==Node::VALID )
                    elements.insert(Element(n1,n2,n3,n4));
#else
                const IntVect ivu = IntVect(iv).shift(BoxLib::BASISV(2));
                const Node& n1 = ifab(iv ,0);
                const Node& n2 = ifab(IntVect(iv ).shift(BoxLib::BASISV(0)),0);
                const Node& n3 = ifab(IntVect(iv ).shift(BoxLib::BASISV(0)).shift(BoxLib::BASISV(1)),0);
                const Node& n4 = ifab(IntVect(iv ).shift(BoxLib::BASISV(1)),0);
                const Node& n5 = ifab(ivu,0);
                const Node& n6 = ifab(IntVect(ivu).shift(BoxLib::BASISV(0)),0);
                const Node& n7 = ifab(IntVect(ivu).shift(BoxLib::BASISV(0)).shift(BoxLib::BASISV(1)),0);
                const Node& n8 = ifab(IntVect(ivu).shift(BoxLib::BASISV(1)),0);
                if (n1.type==Node::VALID && n2.type==Node::VALID &&
                    n3.type==Node::VALID && n4.type==Node::VALID &&
                    n5.type==Node::VALID && n6.type==Node::VALID &&
                    n7.type==Node::VALID && n8.type==Node::VALID )
                    elements.insert(Element(n1,n2,n3,n4,n5,n6,n7,n8));
#endif
            }
        }
    }

    int nElts = elements.size();
    cnt = 0;
    std::cerr << "Before connData allocated " << elements.size() << " elements"  << endl;
    Array<int> connData(MYLEN*nElts);
    std::cerr << "After connData allocated " << elements.size() << " elements" << endl;
    for (EltSet::const_iterator it = elements.begin(); it!=elements.end(); ++it)
        for (int j=0; j<MYLEN; ++j)
            connData[cnt++] = nodeMap[*((*it).n[j])]+1;
            
    std::cerr << "Final elements built" << endl;

    // Invert the map
    std::vector<Node> nodeVect(nodeMap.size());
    for (NodeMap::const_iterator it=nodeMap.begin(); it!=nodeMap.end(); ++it)
        nodeVect[(*it).second] = (*it).first;
    std::cerr << "Final nodeVect built" << endl;
		
    nodeMap.clear();
    elements.clear();
    nodes.clear();

    std::cerr << "Temp nodes, elements cleared" << endl;

    PArray<MultiFab> fileData(Nlev);
    for (int lev=0; lev<Nlev; ++lev)
    {
        fileData.set(lev,new MultiFab(amrData.boxArray(lev),comps.size(),0));
        std::cerr << "My data set alloc'd at lev=" << lev << endl;
        for (int i=0; i<comps.size(); ++i)
        {
            fileData[lev].copy(amrData.GetGrids(lev,comps[i]),0,i,1);
            std::cerr << "After GetGrids: " << amrData.PlotVarNames()[comps[i]] << endl;
            amrData.FlushGrids(comps[i]);
            std::cerr << "AmrData flushed: " << amrData.PlotVarNames()[comps[i]] << endl;
        }
    }
    std::cerr << "File data loaded" << endl;

    int tmpDatLen = (BL_SPACEDIM+comps.size())*nodeVect.size();
    FABdata tmpData(tmpDatLen);
    std::cerr << "Final node data allocated" << endl;
    cnt = 0;
    for (int i=0; i<nodeVect.size(); ++i)
    {
        const Node& node = nodeVect[i];
        const Array<Real> dx = amrData.DxLevel()[node.level];
        const IntVect& iv = node.iv;
    	for (int dir=0; dir<BL_SPACEDIM; ++dir)
            tmpData[cnt++] = amrData.ProbLo()[dir] + (iv[dir] + 0.5) * dx[dir];

        const BoxArray& grids = fileData[node.level].boxArray();
        bool found_it = false;
        int jGrid = -1;
        for (int j=0; j<grids.size() && !found_it; ++j)
        {
            if (grids[j].contains(iv))
            {
                found_it = true;
                jGrid = j;
            }
        }
        BL_ASSERT(found_it);
        for (int n=0; n<comps.size(); ++n)
            tmpData[cnt++] = fileData[node.level][jGrid](iv,n);
    }

    //
    // Write output
    //
    const int nState = BL_SPACEDIM + comps.size();
    std::string vars = D_TERM("X"," Y"," Z");
    for (int j=0; j<comps.size(); ++j)
        vars += " " + amrData.PlotVarNames()[comps[j]];

    if (outType == "tec")
    {
#ifdef USE_TEC_BIN_IO
        INTEGER4 Debug = 0;
        INTEGER4 VIsDouble = 1;
        INTEGER4 EltID = D_PICK(0,1,3);
        TECINI("Pltfile data", (char*)vars.c_str(), (char*)outfile.c_str(), ".", &Debug, &VIsDouble);
        INTEGER4 nPts = nodeVect.size();
        TECZNE((char*)infile.c_str(), &nPts, &nElts, &EltID, "FEPOINT", NULL);
        TECDAT(&tmpDatLen,tmpData.fab.dataPtr(),&VIsDouble);
        TECNOD(connData.dataPtr());
        TECEND();
#else
        std::ofstream osf(outfile.c_str(),std::ios::out);
        osf << D_TERM("VARIABLES= \"X\"", " \"Y\"", " \"Z\"");
        for (int j=0; j<comps.size(); ++j)
            osf << " \""  << amrData.PlotVarNames()[comps[j]] << "\"";
        const int nPts = nodeVect.size();
        char buf[100];
        sprintf(buf,"%g",amrData.Time());
        osf << endl << "ZONE T=\"" << infile << " time = " << buf
            << "\", N=" << nPts << ", E=" << nElts << ", F=FEPOINT ET="
            << D_PICK("POINT","QUADRILATERAL","BRICK") << endl;
        
        for (int i=0; i<nPts; ++i)
        {
            for (int j=0; j<nState; ++j)
                osf << tmpData[i*nState+j] << " ";
            osf << endl;
        }
        for (int i=0; i<nElts; ++i)
        {
            for (int j=0; j<MYLEN; ++j)
                osf << connData[i*MYLEN+j] << " ";
            osf << endl;
        }
        osf << endl;
        osf.close();
#endif
    }
    else
    {

        std::ofstream ofs;
        std::ostream* os =
            (outfile=="-" ? (std::ostream*)(&std::cout) : (std::ostream*)(&ofs) );
        if (outfile!="-")
            ofs.open(outfile.c_str(),std::ios::out);
        (*os) << infile << " time = " << amrData.Time() << endl;
        (*os) << vars << endl;
        (*os) << nElts << " " << MYLEN << endl;
        tmpData.fab.writeOn(*os);
        (*os).write(connData.dataPtr(),sizeof(int)*connData.size());
        if (outfile!="-")
            ofs.close();
    }

    BoxLib::Finalize();
}
