/*****************************************************************************
*
* Copyright (c) 2000 - 2018, Lawrence Livermore National Security, LLC
* Produced at the Lawrence Livermore National Laboratory
* LLNL-CODE-442911
* All rights reserved.
*
* This file is  part of VisIt. For  details, see https://visit.llnl.gov/.  The
* full copyright notice is contained in the file COPYRIGHT located at the root
* of the VisIt distribution or at http://www.llnl.gov/visit/copyright.html.
*
* Redistribution  and  use  in  source  and  binary  forms,  with  or  without
* modification, are permitted provided that the following conditions are met:
*
*  - Redistributions of  source code must  retain the above  copyright notice,
*    this list of conditions and the disclaimer below.
*  - Redistributions in binary form must reproduce the above copyright notice,
*    this  list of  conditions  and  the  disclaimer (as noted below)  in  the
*    documentation and/or other materials provided with the distribution.
*  - Neither the name of  the LLNS/LLNL nor the names of  its contributors may
*    be used to endorse or promote products derived from this software without
*    specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT  HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR  IMPLIED WARRANTIES, INCLUDING,  BUT NOT  LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND  FITNESS FOR A PARTICULAR  PURPOSE
* ARE  DISCLAIMED. IN  NO EVENT  SHALL LAWRENCE  LIVERMORE NATIONAL  SECURITY,
* LLC, THE  U.S.  DEPARTMENT OF  ENERGY  OR  CONTRIBUTORS BE  LIABLE  FOR  ANY
* DIRECT,  INDIRECT,   INCIDENTAL,   SPECIAL,   EXEMPLARY,  OR   CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT  LIMITED TO, PROCUREMENT OF  SUBSTITUTE GOODS OR
* SERVICES; LOSS OF  USE, DATA, OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER
* CAUSED  AND  ON  ANY  THEORY  OF  LIABILITY,  WHETHER  IN  CONTRACT,  STRICT
* LIABILITY, OR TORT  (INCLUDING NEGLIGENCE OR OTHERWISE)  ARISING IN ANY  WAY
* OUT OF THE  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
*****************************************************************************/

// ************************************************************************* //
//                            avtVLIFileFormat.C                             //
// ************************************************************************* //

#include <VLIFileManager.h>
#include <avtVLIFileFormat.h>
#include <avtDataRangeSelection.h>

#include <string>
#include <sstream>
#include <vector>

#include <vtkObject.h>
#include <vtkVertex.h>
#include <vtkPoints.h>
#include <vtkFloatArray.h>
#include <vtkRectilinearGrid.h>
#include <vtkUnstructuredGrid.h>

#include <avtDatabaseMetaData.h>

#include <Expression.h>

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

#include <avtParallel.h>
#ifdef PARALLEL
  #include <mpi.h>
#endif

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>

using std::string;

#define VLIDEBUG debug5 << "VLIPLUGIN: "

// ****************************************************************************
//  Method: AllocateSocket
//          AcceptConnection
//          RequestConnection
//          InvokeRemoteCall
//          ServerRead
//
//  Purpose:
//      Several utility functions to help setup client/server functionality.
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
//    Jeremy Meredith, Thu Aug  7 14:45:49 EDT 2008
//    Use %ld for size_t variables.
//
// ****************************************************************************

int AllocateSocket(std::string hostname, int port) {
  struct sockaddr_in sn;
  int s, bindStatus = -1;
  struct hostent *he;

  bzero((char*)&sn, sizeof(sn));
  sn.sin_family = AF_INET;
  sn.sin_port = htons((short)port);
  if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1;
  if (!(he = gethostbyname(hostname.c_str()))) return -1;
  bindStatus = bind(s, (struct sockaddr*)&sn, sizeof(sn));
  if(bindStatus == -1) {
    sn.sin_addr = *(struct in_addr*)(he->h_addr_list[0]);
    bindStatus = bind(s, (struct sockaddr*)&sn, sizeof(sn));
    if (bindStatus == -1) return -1;
  }

  return s;
}

int AcceptConnection(int s) { 
  struct sockaddr_in sn;
  socklen_t le = sizeof(sn);

  if (listen(s, 1) == -1) return -1;
  sn.sin_family = AF_INET;
  int a = accept(s, (struct sockaddr*)&sn, &le);
  VLIDEBUG << "**** Accepted connection from " << inet_ntoa(sn.sin_addr) << ":" << ntohs(sn.sin_port) << endl;
  return a;
}

int RequestConnection(char *host, int port) {
  struct sockaddr_in sn;
  int s;
  struct hostent *he;

  if (!(he = gethostbyname(host))) return -1;   sn.sin_family = AF_INET;
  sn.sin_port  = htons((short)port);
  sn.sin_addr.s_addr = *(u_long*)(he->h_addr_list[0]);
  if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1;
  if (connect(s, (struct sockaddr*)&sn, sizeof(sn)) == -1) return -1;
  return s;
}

int InvokeRemoteCall(std::string host, int port, int call) {
    int conn;  
    char in_buffer[4], out_buffer[4];
    char *server = (char *)host.c_str();

    conn = RequestConnection(server, port);

    sprintf(out_buffer, "%.3d", call);
    if (write(conn, out_buffer, 4) < 0)
    {
        int eno = errno;
        debug1 << "write failed " << strerror(eno) << endl;
    }


    if (read(conn, in_buffer, 4) == 4) {
        if (atoi(in_buffer)) {
            VLIDEBUG << "InvokeRemoteCall(): Client couldn't call server function " << (call ? "Count." : "Query.") << endl;
            return -1;
        }
    } else {
        VLIDEBUG << "InvokeRemoteCall(): Client could not connect to server correctly." << endl;
        return -1;
    }

    return conn;
}

size_t ServerRead(int fd, void *buf, size_t count, int wait) {
        size_t nread = 0;
        if (!wait) wait = 1000;
        for(nread=0;nread<count&&wait;wait=(wait<0?-1:wait-1))
                nread += read(fd, ((unsigned char *)buf)+nread, count-nread);
        if(nread-count) {
                char tmp[500];
                sprintf(tmp, "ServerRead(): \"read\" from file descriptor %d returned only %ld Bytes instead of %ld Bytes!\n", fd, nread, count);
                VLIDEBUG << tmp;
        }
        return nread;
}

// ****************************************************************************
//  Method: GatherString
//          GatherInt
//
//  Purpose:
//      MPI utility functions to gather data.
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

void GatherString(std::string *s, int myrank, int numproc) {
#ifdef PARALLEL
    int len;
    char buffer[1024];

    if (myrank != 0) 
    {   
        len = s[myrank].length(); 
        MPI_Send(&len, 1, MPI_INT, 0, 0, VISIT_MPI_COMM);
        MPI_Send((void*)(s[myrank].c_str()), len, MPI_CHAR, 0, 0, VISIT_MPI_COMM);
    }
    
    if (myrank == 0)
    {
        for(int i = 1; i < numproc; ++i)
        {
                MPI_Status status;
                memset(buffer, 0, 1024);
                MPI_Recv(&len, 1, MPI_INT, i, 0, VISIT_MPI_COMM, &status);
                MPI_Recv(buffer, len, MPI_CHAR, i, 0, VISIT_MPI_COMM, &status);
                s[i].append(buffer);
        }
    }
#endif
}

void GatherInt(int *s, int myrank, int numproc) {
#ifdef PARALLEL
    if (myrank != 0) 
    {   
        MPI_Send(&s[myrank], 1, MPI_INT, 0, 0, VISIT_MPI_COMM);
    }
    
    if (myrank == 0)
    {
        for(int i = 1; i < numproc; ++i)
        {
                MPI_Status status;
                MPI_Recv(&s[i], 1, MPI_INT, i, 0, VISIT_MPI_COMM, &status);
        }
    }
#endif
}

// ****************************************************************************
//  Method: threadStarter
//          pacerStarter
//
//  Purpose:
//      thread utility functions to startup threads.
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

void *threadStarter(void *in) {
    return ((avtVLIFileFormat *)in)->threadCommServer(NULL);
}

void *pacerStarter(void *in) {
    return ((avtVLIFileFormat *)in)->threadPacer(NULL);
}

// ****************************************************************************
//  Method: avtVLI constructor
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

avtVLIFileFormat::avtVLIFileFormat(const char *filename)
    : avtMTMDFileFormat(filename)
{    
    // INITIALIZE DATA MEMBERS
    VLIDEBUG << "---- avtVLIFileFormat(const char *filename = \"" 
             << filename << "\")" << endl;
    initialized = 0;
    procNum = PAR_Rank();
    procCount = PAR_Size();
    error = info = loaded = false;
    sport = NULL;
    shostname = NULL;
    ehostname = new std::string[procCount];
    eport = new int[procCount];
    socket = -1;
    ptid = NULL;
    selList = std::vector<avtDataSelection_p>();
    selsApplied = NULL;
    registeredVars = std::vector<int>();
    queryTimestate = -1;
    queryDomain = -1;
    queryObjects = std::vector<vtkObject *>();
    pacertid = NULL;
    pport = -1;
    inQuery = false;

#ifndef PARALLEL
    // Check for valid parallel environment
    if (procCount > 1) 
    {
        VLIDEBUG << "!!!! avtVLIFileFormat(): More than one engine, "
                 << "but PARALLEL flag not set! Recompile vli library " 
                 << "with -DPARALLEL flag." << endl;
        EXCEPTION1(InvalidDBTypeException, "More than one engine, but " 
                   "PARALLEL flag not set! Recompile vli library with " 
                   "-DPARALLEL flag.");
    }
#endif

    // Read in configuration file
    config = new VLIFileManager();
    if (config->ReadConfigFile(filename) == NULL) {
        // Error: File could not be read
        VLIDEBUG << "!!!! avtVLIFileFormat(): File \"" << filename 
                 << "\" corrupt or unreadable." << endl;
        delete config;
        EXCEPTION1(InvalidDBTypeException, "Either the file could not "
                   "be read or it contains corrupt data");
    } else {
        initialized = 1;
        startServers();
   }
}

// ****************************************************************************
//  Method: avtVLI destructor
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

avtVLIFileFormat::~avtVLIFileFormat() {
        int conn;
        VLIDEBUG << "---- ~avtVLIFileFormat()...";
        if (procNum == 0) {
                conn = InvokeRemoteCall(shostname[0], sport[0], 998);
                if (conn != -1) close(conn);
        }
        ClearCache();
        delete[] ehostname;
        delete[] eport;
        if (sport) delete[] sport;
        if (shostname) delete[] shostname;
        if (ptid) delete[] ((pthread_t *)ptid);
        if (pacertid) delete[] ((pthread_t *)pacertid);
        VLIDEBUG << " done!" << endl;
}

// ****************************************************************************
//  Method: avtVLIFileFormat::threadCommServer
//
//  Purpose:
//      communication thread for feedback from vliserver
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

void *avtVLIFileFormat::threadCommServer(void *in) {
        VLIDEBUG << "**** Thread started: " << endl;
        int socket = this->socket;
        char tmp[500];
        
        while (1)
        {
                int conn = AcceptConnection(socket);
                char c = '\0', d = '\0', buf[500], sbuf[255];
                memset(buf, 0, 500);
                VLIDEBUG << "**** Serving socket " << socket << " through connection " << conn << "." << endl;
                ServerRead(conn, &c, 1, 1000);
                if (c == 'i') //exchange info and approve
                {
                        VLIDEBUG << "**** Checking information" << endl;
                        d = '1';
                        ServerRead(conn, buf, 39, -1);
                        char *dbuf = strdup(buf);
                        int _nservers = atoi(&dbuf[36]); dbuf[36] = 0;
                        int _nitems = atoi(&dbuf[21]); dbuf[21] = 0;
                        int _nattr = atoi(&dbuf[18]); dbuf[18] = 0;
                        int _itemsz = atoi(&dbuf[15]); dbuf[15] = 0;
                        int _dims[3];
                        _dims[2] = atoi(&dbuf[11]); dbuf[11] = 0;
                        _dims[1] = atoi(&dbuf[7]); dbuf[7] = 0;
                        _dims[0] = atoi(&dbuf[3]); dbuf[3] = 0;
                        int _files = atoi(dbuf);
                        sprintf(tmp, "**** nservers = %d, nitems = %d, nattr = %d, itemsz = %d, dims = %d %d %d, files = %d\n", _nservers, _nitems, _nattr, _itemsz, _dims[0], _dims[1], _dims[2], _files);
                        VLIDEBUG << tmp;
                        
                        this->shostname = new std::string[_nservers];
                        this->sport = new int[_nservers];
                        for(int i=0; i<_nservers; ++i)
                        {
                                char *st = NULL;
                                ServerRead(conn, sbuf, 255, -1);
                                if ((st = strstr(sbuf, ":")) != NULL) {
                                        *st++ = 0; 
                                        this->sport[i] = atoi(st);
                                        this->shostname[i] = std::string(sbuf);
                                } else d = '0';
                                
                                sprintf(tmp, "**** Server %d: %s, port %d\n", i, this->shostname[i].c_str(), this->sport[i]);
                                VLIDEBUG << tmp;
                        }
                        
                        if ((_nservers != this->config->getNoDataServers()) ||
                            (_nitems   != this->config->getDataset()->getNItems()) ||
                            (_nattr    != this->config->getDataset()->getNAttributes()) ||
                            (_files    != 1)
                           ) {
                                d = '0';
                                VLIDEBUG << "**** Information mismatch!" << endl;
                        }
                        
                        if (this->procNum == 0) {
                                if (write(conn, &d, 1) < 0)
                                {
                                    int eno = errno;
                                    debug1 << "write failed " << strerror(eno) << endl;
                                }
                                VLIDEBUG << "**** Approval notice sent (" << d << ")" << endl;
                        }
                        if (d == '1') this->info = true; 
                }
                if (c == 'l') // files loaded successfully
                {
                        VLIDEBUG << "**** Files loaded successfully" << endl;
                        this->loaded = true;
                        pacertid = (void *) new pthread_t[1];
                        if (procNum == 0)        
                                pthread_create( (pthread_t *)pacertid, NULL, pacerStarter, (void *)this);
                }
                if (c == 'e') // indicates error
                {
                        VLIDEBUG << "**** Server crashed!" << endl;
                        this->error = true; this->info = false; this->loaded = false;
                }
                close(conn);
                if (this->procNum == 0) {
                        // Forward messages to all comm servers
                        for (int i = 1; i < this->procCount; ++i) {
                                int c2 = RequestConnection((char *)this->ehostname[i].c_str(), this->eport[i]);
                                if (write(c2, &c, 1) < 0)
                                {
                                    int eno = errno;
                                    debug1 << "write failed " << strerror(eno) << endl;
                                }
                                if (c == 'i') {
                                        if (write(c2, buf, 39) < 0)
                                        {
                                            int eno = errno;
                                            debug1 << "write failed " << strerror(eno) << endl;
                                        }
                                        char sbuf[255]; 
                                        for (int j = 0; j < this->config->getNoDataServers(); ++j) {
                                                sprintf(sbuf, "%s:%d", this->shostname[j].c_str(), this->sport[j]);
                                                if (write(c2, sbuf, 255) < 0)
                                                {
                                                    int eno = errno;
                                                    debug1 << "write failed " << strerror(eno) << endl;
                                                }

                                        }
                                }
                        }
                        VLIDEBUG << "**** Message forwarded" << endl;
                }
                if ((c == 'e') || ((c == 'i') && (d == '0'))) {
                        // on error or information mismatch no more need for this thread as servers have shut down
                        VLIDEBUG << "**** Thread stopping NOW" << endl;
                        close(socket);
                        shutdown(socket, SHUT_RDWR);
                        this->socket = -1;
                        return(NULL); // same then pthread_exit(NULL)
                }
        }
}

// ****************************************************************************
//  Method: avtVLIFileFormat::startCommServer
//
//  Purpose:
//      port allocation and startup for new comm thread
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

int avtVLIFileFormat::startCommServer(std::string hostname) {
        srand(time(NULL) + procNum + 17);
        int port = 1024 + rand() % 8196;
        
        while ((socket = AllocateSocket(hostname, port)) == -1) {
                port = 1024 + rand() % 8196;
        }
        
        ptid = (void *) new pthread_t[1];
        pthread_create( (pthread_t *)ptid, NULL, threadStarter, (void *)this);
        
        return port;
}

// ****************************************************************************
//  Method: avtVLIFileFormat::threadPacer
//
//  Purpose:
//      heartbeat generator thread for vliserver
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

void *avtVLIFileFormat::threadPacer(void *in) {
        VLIDEBUG << "&&&& Pacer started " << endl;
        while (1) {
                while (inQuery) sleep(1);
                if (! inQuery) { 
                        int c = InvokeRemoteCall(shostname[0], sport[0], 2);
                        if (c != -1) close(c);
                }
                sleep(30);
        }
        return NULL;
}

// ****************************************************************************
//  Method: avtVLIFileFormat::startServers
//
//  Purpose:
//      starts up vliservers, comm threads, and heartbeat generator
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

void avtVLIFileFormat::startServers(void) {
#ifndef MDSERVER
        VLIDEBUG << "---- avtVLIFileFormat() is starting up "
                 << "communication servers now." << endl;
        char hn[255];
        gethostname(hn, 255u);
        struct hostent *hostinfo = gethostbyname(hn);
        strncpy(hn, hostinfo->h_name, 255);
        ehostname[procNum] = std::string(hn);
        eport[procNum] = startCommServer(ehostname[procNum]);
        // Gather other engine hostnames and comm ports (on node 0 only)
        GatherString(ehostname, procNum, procCount);
        GatherInt(eport, procNum, procCount);
        
        if (procNum == 0) {
                std::string syscall = std::string(config->getSyscall());
                std::string::size_type loc = syscall.find( "{NSERVERS}", 0 );
                std::stringstream oss; 
                oss << config->getNoDataServers();
                std::string nserv = std::string(oss.str());
                if (loc != std::string::npos)
                {
                        syscall.replace(loc, 10, nserv, 0, nserv.length());
                }
                loc = syscall.find( "{SERVER}" , 0 );
                nserv = std::string(config->getServer());
                nserv.append(" ");
                
                nserv.append(ehostname[procNum]);
                nserv.append(":");
                oss.str("");
                oss << eport[procNum];
                nserv.append(oss.str());
                
                nserv.append(" ");
                nserv.append(config->getDatafile());
                if (loc != std::string::npos)
                {
                        syscall.replace(loc, 8, nserv, 0, nserv.length());
                }
                
                syscall.append(" &");
                VLIDEBUG << "---- executing \"" << syscall.c_str() << "\"\n";
                int rv = system(syscall.c_str()); (void)rv;
        }
#endif
}

// ****************************************************************************
//  Method: avtEMSTDFileFormat::GetNTimesteps
//
//  Purpose:
//      Tells the rest of the code how many timesteps there are in this file.
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

int avtVLIFileFormat::GetNTimesteps(void) {
    VLIDEBUG << "---- GetNTimesteps() returns ";
    
    if (!initialized) return 1; // returning 0 throws error, that's why.
    
    // Check for time dimension
    const int *axis = config->getAxis();
    int t = axis[3];
    delete[] axis;
    
    if (t == -1) return 1; // no time dimension given, so single timestep
    
    // Time dimension given, retrieve information
    VLIAttribute *a = config->getDataset()->getAttribute(t);
    
    int n = (int)(1 + (int)(a->GetMax() - a->GetMin()));
    
    VLIDEBUG << n << endl; 
    
    return n;
    
}

// ****************************************************************************
//  Method: avtVLIFileFormat::FreeUpResources
//
//  Purpose:
//      When VisIt is done focusing on a particular timestep, it asks that
//      timestep to free up any resources (memory, file descriptors) that
//      it has associated with it.  This method is the mechanism for doing
//      that.
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

void avtVLIFileFormat::FreeUpResources(void) {
    // NOTHING TO BE DONE HERE (PER TIMESTEP)
}

// ****************************************************************************
//  Method: avtVLIFileFormat::PopulateDatabaseMetaData
//
//  Purpose:
//      This database meta-data object is like a table of contents for the
//      file.  By populating it, you are telling the rest of VisIt what
//      information it can request from you.
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

void avtVLIFileFormat::PopulateDatabaseMetaData(avtDatabaseMetaData *md, int timeState) {
    VLIDEBUG << "---- PopulateDatabaseMetaData(avtDatabaseMetaData *md = " 
             << md << ", int timeState = " << timeState << ")" << endl;
    
    if (!initialized) return;
    
    VLIDataset* ds = config->getDataset();
    
    std::string meshname =std::string(config->getName());
    const int *axis = config->getAxis();
    int dims = 0;
    
    for(int i = 0; i < 3; ++i) if (axis[i] != -1) ++dims;
    
    avtMeshType mt = AVT_POINT_MESH; 
    double *extents = NULL;
    int nblocks = procCount;
    int origin = 0;
    
    VLIDEBUG << "---- Adding Mesh: " << meshname << ", meshtype: " 
       << (mt == AVT_POINT_MESH ? "AVT_POINT_MESH" : "AVT_RECTILINEAR_MESH") 
       << ", nblocks: " << nblocks << ", origin: " << origin 
       << ", Dimensions: " << dims << endl;
    
    AddMeshToMetaData(md, meshname, mt, extents, nblocks, origin, dims, dims);
    
    int numVar = ds->getNAttributes();

    VLIDEBUG << "---- Adding " << numVar << " Vars: ";
        
    for(int j = 0; j < numVar; ++j)
    {
        std::string varName = std::string(ds->getAttribute(j)->GetName());
        AddScalarVarToMetaData(md, varName, meshname, AVT_NODECENT);

        VLIDEBUG << varName << (j!=numVar-1?", ":"\n"); 
    }
    
}

// ****************************************************************************
//  Method: avtVLIFileFormat::convertToFloat
//
//  Purpose:
//      converts unsigned short value to corresponding float value
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

float avtVLIFileFormat::ConvertToFloat(unsigned short data, int attrib) {
        if (config->getDataset()->getAttribute(attrib)->GetIsScaled()==false)
                return (float)data;

        double min = config->getDataset()->getAttribute(attrib)->GetMin();
        double max = config->getDataset()->getAttribute(attrib)->GetMax();

        return ((((double)data) / 65535.0) * (max - min) + min);
}

// ****************************************************************************
//  Method: IsInVector
//
//  Purpose:
//      checks for a given value in a std::vector<int>
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

int IsInVector(std::vector<int> v, int i) {
        int nv = static_cast<int>(v.size());
        for (int k = 0; k < nv; ++k) if (v[k] == i) return k;
        return -1;
}

// ****************************************************************************
//  Method: avtVLIFileFormat::ClearCache
//
//  Purpose:
//      clears the internal data cache
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

void avtVLIFileFormat::ClearCache() {
    VLIDEBUG << "---- Clearcache()... ";
    size_t i, nattr = config->getDataset()->getNAttributes();
    if (queryObjects.size() < nattr + 1) {
        queryObjects.resize(nattr + 1, NULL);
        VLIDEBUG << "done!" << endl;
        return;
    }
    for (i = 0; i < nattr; ++i) {
        if (queryObjects[i] != NULL) {
            queryObjects[i]->Delete();
            queryObjects[i] = NULL;
        }
    }
    if (queryObjects[i] != NULL) {
        queryObjects[i]->Delete();
        queryObjects[i] = NULL;
    }
    VLIDEBUG << "done!" << endl;
}

// ****************************************************************************
//  Method: avtVLIFileFormat::Query
//
//  Purpose:
//      performs actual queries to vliserver for GetMesh and GetVar
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

vtkObject *avtVLIFileFormat::Query(int timestate, int domain, const char *varname) {
        int i, j, k, var = -1;
        int nattr = this->config->getDataset()->getNAttributes();
        VLIDataset* ds = config->getDataset();

        VLIDEBUG<<"---- Query(int timestate = "<<timestate
                <<", int domain = "<<domain<<", const char *varname = \""
                <<(varname?varname:"(null)")<<"\")\n"<<endl;
        
        // Initialize queryObject if necessary
        if (static_cast<int>(queryObjects.size()) < nattr + 1) {
                queryObjects.resize(nattr + 1, NULL);
        }

        // Check timestate and domain, clear queryObjects if necessary
        if ( (timestate != queryTimestate) || (domain != queryDomain) ) {
                ClearCache();
                queryTimestate = timestate;
                queryDomain = domain;
        }

        // Set var <- varname index or "mesh"
        if (varname != NULL) {
                // Find index of varname -> var
                for(i = 0; i < nattr; ++i)
                {
                        std::string getName = std::string(varname);
                        if (getName.compare(ds->getAttribute(i)->GetName()) == 0) var = i;
                }
        
                // Raise error if no recognized variable
                if (var == -1)
                {
                        EXCEPTION1(InvalidVariableException, varname);
                        return NULL;
                }
        } else {
            var = nattr;
        }

        // Check cache (hit: deliver, but delete from cache)
        if (queryObjects[var] != NULL) {
                vtkObject *rval = queryObjects[var];
                queryObjects[var] = NULL;
                return rval;
        }


        // Cache did not contain object -> retrieve it
        // When retrieving retrieve registered variables that are not in cache

        // Set up vector with primary request
        std::vector<int> request;
        if (var != nattr) {
                // Primary request is single var
                request.push_back(var);
        } else {
                // Primary request is mesh
                const int *axes = config->getAxis();
                for (i = 0; i < 3; ++i) {
                        if (axes[i] != -1) request.push_back(axes[i]);
                }
                delete[] axes;
        }

        // Now add registered vars (if not contained already or in cache)
        int dims = 0;
        const int *axis = config->getAxis();
        for(i = 0; i < 3; ++i) if (axis[i] != -1) dims++;
        int nRegisteredVars = static_cast<int>(registeredVars.size());
        for (i = 0; i < nRegisteredVars; ++i) {
                int v = registeredVars[i];
                if ( (IsInVector(request, v) < dims) && (queryObjects[v] == NULL) )
                        request.push_back(v);
        }

        VLIDEBUG << "---- Request: { ";
        int nRequest = static_cast<int>(request.size());
        for(i = 0; i < nRequest; ++i) VLIDEBUG << request[i] << ", ";
        VLIDEBUG << "}" << endl;

        // Init bounds
        int *bounds = new int[2 * nattr];
        for(i = 0; i < nattr; ++i) {
                bounds[2 * i + 0] = 0;
                bounds[2 * i + 1] = 65535;
        }
        
        // Restrict time
        int time = -1;
        if (axis[3] != -1) {
                time = timestate + (int)config->getDataset()->getAttribute(axis[3])->GetMin();
                bounds[2 * axis[3]] = bounds[2 * axis[3] + 1] = time;
        }

        // Restrict Threshold variables
        int nSelList = static_cast<int>(selList.size());
        for(i = 0; i < nSelList; ++i)
        {
                if (std::string(selList[i]->GetType()) == std::string("Data Range Selection"))
                {
                        avtDataRangeSelection *dr = (avtDataRangeSelection *) *(selList[i]);
                        int varn = -1;
                        for(j = 0; j < nattr; ++j) {
                                if ((dr->GetVariable()).compare(ds->getAttribute(j)->GetName()) == 0) varn = j;
                        }
                        double min = dr->GetMin();
                        double max = dr->GetMax();
                        if (varn != -1) {
                                // Set bounds[2 * varn] = "min", bounds[2 * varn + 1] = "max"
                                double varmax = ds->getAttribute(varn)->GetMax();
                                double varmin = ds->getAttribute(varn)->GetMin();
                                bool varscaled = ds->getAttribute(varn)->GetIsScaled();

                                if (max > varmax) max = varmax;
                                if (max < varmin) max = varmin;
                                if (min > varmax) min = varmax;
                                if (min < varmin) min = varmin;

                                int val;
                                val=(int)((min - varmin)/(varmax-varmin)*65536);
                                if (varscaled == false) val = (int)min;
                                bounds[2 * varn + 0] = val;
                                
                                val=(int)((max - varmin)/(varmax-varmin)*65536);
                                if (varscaled == false) val = (int)max;
                                bounds[2 * varn + 1] = val;

                                //Mark selection as applied
                                (*selsApplied)[i] = true; 
                        }
                }
        }
        
        // Break up query in domains
        // Break up point: largest range with lowest index
        int nservers = procCount;
        int diff = -1;
        int indx = -1;
        int run = 0;
        for(i = 0; i < nattr; ++i) {
                int d_i = bounds[2*i+1] - bounds[2*i] + 1;
                if (d_i > diff) diff = d_i, indx = i;
                if (diff == 65536) break;
        }
        for(i = nservers; i > domain; --i) {
                run = diff / i;
                if ( (diff % i) > (i / 2) ) ++run;
                diff -= run;
        }
        bounds[2 * indx + 1] = bounds[2 * indx] + diff + run - 1;
        bounds[2 * indx] += diff;

        for(i=0;i<nattr;++i) VLIDEBUG << "bounds[" << i << "] = (" << bounds[2*i] << ", " << bounds[2*i+1] << ")" << endl; 
        
        // Request items from server, retrieve number of nodes
        int *conn = new int[nservers];
        int *n = new int[nservers];
        int nnodes = 0;
        unsigned short data;

        inQuery = true;

        for(i = 0; i < nservers; ++i) {
                conn[i] = InvokeRemoteCall(this->shostname[i], this->sport[i], 0);
                if (write(conn[i], &nRequest, sizeof(int)) < 0)
                {
                    int eno = errno;
                    debug1 << "write failed " << strerror(eno) << endl;
                }
                for(j = 0; j < nRequest; ++j)
                {
                    if (write(conn[i], &request[j], sizeof(int)) < 0)
                    {
                        int eno = errno;
                        debug1 << "write failed " << strerror(eno) << endl;
                    }
                }
                if (write(conn[i], bounds, 2 * nattr * sizeof(int)) < 0)
                {
                    int eno = errno;
                    debug1 << "write failed " << strerror(eno) << endl;
                }
        }
        for(i = 0; i < nservers; ++i) {
                ServerRead(conn[i], &n[i], sizeof(int), -1);
                nnodes += n[i];
        }

        // Initiate data structures for data items
        float **cnt = new float*[nattr];
        for (i=0;i<nattr;++i) cnt[i] = NULL; //DEBUG!
        if (var == nattr) {
                // Create vtkPoints object.
                vtkPoints *points = vtkPoints::New();
                queryObjects[var] = (vtkObject *)points;
                points->SetNumberOfPoints(nnodes);
                cnt[nattr] = (float *) points->GetVoidPointer(0);
                i = dims;
        } else i = 0; 

        for(; i < nRequest; ++i) {
                vtkFloatArray *arr = vtkFloatArray::New();
                queryObjects[request[i]] = (vtkObject *)arr;
                arr->SetNumberOfTuples(nnodes);
                cnt[request[i]] = (float *) arr->GetVoidPointer(0);
        }
        
        // Read in data items from server
        for(i = 0; i < nservers; ++i) {
                for(j = k = 0; j < n[i]; ++j) {
                        if (var == nattr) {
                                for(k = 0; k < 3; ++k) {
                                        if (k < dims) {
                                                ServerRead(conn[i], &data, sizeof(unsigned short), -1);
                                                *(cnt[nattr])++ = ConvertToFloat(data, axis[k]);
                                        } else {
                                                *(cnt[nattr])++ = .0f;
                                        }
                                }
                        }
                        for(;k < nRequest; ++k) {
                                ServerRead(conn[i], &data, sizeof(unsigned short), -1);
                                *(cnt[request[k]])++ = ConvertToFloat(data, request[k]);
                        }
                }
                close(conn[i]);
        }

        inQuery = false;

        if (var == nattr) {
                // Create a vtkUnstructuredGrid to contain the points
                vtkUnstructuredGrid *ugrid = vtkUnstructuredGrid::New();
                ugrid->SetPoints((vtkPoints *)queryObjects[var]);
                ((vtkPoints *)queryObjects[var])->Delete();
                ugrid->Allocate(nnodes);
                vtkIdType _1vertex;
                for(i = 0; i < nnodes; ++i) {
                        _1vertex = i;
                        ugrid->InsertNextCell(VTK_VERTEX, 1, &_1vertex);
                }
                queryObjects[var] = (vtkObject *)ugrid;
        }

        // Clean up
        delete[] cnt;
        delete[] n;
        delete[] bounds;
        delete[] conn;
        delete[] axis;

        // return data (now in cache)
        vtkObject *rv = queryObjects[var];
        queryObjects[var] = NULL;
        return rv;
}

// ****************************************************************************
//  Method: avtVLIFileFormat::GetMesh
//
//  Purpose:
//      Gets the mesh associated with this file.  The mesh is returned as a
//      derived type of vtkDataSet (ie vtkRectilinearGrid, vtkStructuredGrid,
//      vtkUnstructuredGrid, etc).
//
//  Arguments:
//      timestate   The index of the timestate.  If GetNTimesteps returned
//                  'N' time steps, this is guaranteed to be between 0 and N-1.
//      domain      The index of the domain.  If there are NDomains, this
//                  value is guaranteed to be between 0 and NDomains-1,
//                  regardless of block origin.
//      meshname    The name of the mesh of interest.  This can be ignored if
//                  there is only one mesh.
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

vtkDataSet *avtVLIFileFormat::GetMesh(int timestate, int domain, const char *meshname) {
        // meshname will be ignored
        VLIDEBUG<<"---- GetMesh(int timestate = "<<timestate<<", int domain = "<<domain<<", const char *meshname = \""<<meshname<<"\")\n"<<endl;

        while (!loaded) sleep(3);

        return (vtkDataSet *)Query(timestate, domain, NULL);
}

// ****************************************************************************
//  Method: avtVLIFileFormat::GetVar
//
//  Purpose:
//      Gets a scalar variable associated with this file.  Although VTK has
//      support for many different types, the best bet is vtkFloatArray, since
//      that is supported everywhere through VisIt.
//
//  Arguments:
//      timestate  The index of the timestate.  If GetNTimesteps returned
//                 'N' time steps, this is guaranteed to be between 0 and N-1.
//      domain     The index of the domain.  If there are NDomains, this
//                 value is guaranteed to be between 0 and NDomains-1,
//                 regardless of block origin.
//      varname    The name of the variable requested.
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

vtkDataArray *avtVLIFileFormat::GetVar(int timestate, int domain, const char *varname) {
        VLIDEBUG << "---- GetVar(int timestate = " << timestate << ", int domain = " << domain << ", const char *varname = \"" << varname << "\")" << endl;

        while (!loaded) sleep(3);

        return (vtkDataArray *)Query(timestate, domain, varname);
}

// ****************************************************************************
//  Method: avtVLIFileFormat::CanCacheVariable
//
//  Purpose:
//      Determines whether the generic database is allowed to cache data.
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

bool avtVLIFileFormat::CanCacheVariable(const char *) {
    return false;
}

// ****************************************************************************
//  Method: avtVLIFileFormat::GetVectorVar
//
//  Purpose:
//      Gets a vector variable associated with this file.  Although VTK has
//      support for many different types, the best bet is vtkFloatArray, since
//      that is supported everywhere through VisIt.
//
//  Arguments:
//      timestate  The index of the timestate.  If GetNTimesteps returned
//                 'N' time steps, this is guaranteed to be between 0 and N-1.
//      domain     The index of the domain.  If there are NDomains, this
//                 value is guaranteed to be between 0 and NDomains-1,
//                 regardless of block origin.
//      varname    The name of the variable requested.
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

vtkDataArray *avtVLIFileFormat::GetVectorVar(int timestate, int domain,const char *varname) {
    // NO VECTOR VARIABLES
    return 0;
}

// ****************************************************************************
//  Method: avtVLIFileFormat::RegisterDataSelections
//
//  Purpose:
//      registers any data selection with the plugin
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

void avtVLIFileFormat::RegisterDataSelections(const std::vector<avtDataSelection_p> &sels, std::vector<bool> *selectionsApplied) {
    this->selList = sels;
    this->selsApplied = selectionsApplied;

    ClearCache();

    for(size_t i = 0; i < sels.size(); ++i)
    {
        VLIDEBUG << "RegisterDataSelections(): " << sels[i]->GetType();
        if (std::string(sels[i]->GetType()) == "Data Range Selection")
        {
            avtDataRangeSelection *dr = (avtDataRangeSelection *) *(sels[i]);
            VLIDEBUG << " [ \"" << (dr->GetVariable()).c_str() << "\" (" 
                 << dr->GetMin() << ", " << dr->GetMax() << ") ]" << endl;
        }
        else VLIDEBUG << endl;
    }
}

// ****************************************************************************
//  Method: avtVLIFileFormat::RegisterVariableList
//
//  Purpose:
//      registers complete set of variables with the plugin
//
//  Programmer: Markus Glatter <glatter@cs.utk.edu> -- generated by xml2avt
//  Creation:   Mon May 7 13:54:06 PST 2007
//
// ****************************************************************************

void avtVLIFileFormat::RegisterVariableList(const char *primVar, const std::vector<CharStrRef> &vars2nd) {
    std::vector<std::string> vars;

    // Temporarily put variables in a single vector for ease of access
    vars.push_back(std::string(primVar));
    for(size_t i = 0; i < vars2nd.size(); ++i)
        vars.push_back(std::string(*(vars2nd[i])));

    // Now loop through vector to mark registered known variables for class
    registeredVars.clear();
    for(size_t i = 0; i < vars.size(); ++i)
    {
        // Find variable
        VLIDataset* ds = config->getDataset();
        size_t cnt = ds->getNAttributes();
        int nv = -1;
        
        for(size_t j = 0; j < cnt; ++j)
            if (vars[i].compare(ds->getAttribute(j)->GetName()) == 0) nv = j;
        
        // If found, register variable
        if (nv != -1) registeredVars.push_back(nv);
    }

    VLIDEBUG << "RegisterVariableList(): ";
    for(size_t j=0;j<registeredVars.size();++j) VLIDEBUG<<registeredVars[j]<<", ";
    VLIDEBUG << endl;
}

