/*****************************************************************************
*
* 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.
*
*****************************************************************************/

// ************************************************************************* //
//                            avtSASFileFormat.C                             //
// ************************************************************************* //

#include <avtSASFileFormat.h>

#include <string>
#include <float.h>

#include <vtkFloatArray.h>
#include <vtkRectilinearGrid.h>
#include <vtkUnstructuredGrid.h>
#include <vtkCellType.h>

#include <avtDatabaseMetaData.h>

#include <Expression.h>

#include <InvalidVariableException.h>
#include <InvalidFilesException.h>
#include <InvalidDBTypeException.h>
#include <avtCallback.h>

using namespace std;
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

// ****************************************************************************
//  Method: avtSASFileFormat constructor
//
//  Programmer: dbremer -- generated by xml2avt
//  Creation:   Wed Jul 18 12:38:36 PDT 2007
//
//  Modifications:
//    David Bremer, Sep 7, 2007
//    Changed file io api to handle large files.
// ****************************************************************************

avtSASFileFormat::avtSASFileFormat(const char *filename)
    : avtMTMDFileFormat(filename)
{
    nAssemblyTypes = 0;
    aAssemblyTypes = NULL;

    nAssemblys = 0;
    iAssemblyDiskLoc = 0;

    aChannels = NULL;
    iTimeStepSize = 0;
    eChannelOrder = NO_CHANNELS;

    const char *ext = strrchr(filename, '.');
    if (ext)
    {
        if (strcmp(ext, ".sasgeom") == 0)
        {
            geomFileName.assign(filename);
            dataFileName.assign(filename, geomFileName.size() - strlen(".sasgeom"));
            dataFileName.append(".sasdata");
        }
        else if (strcmp(ext, ".sasdata") == 0)
        {
            dataFileName.assign(filename);
            geomFileName.assign(filename, dataFileName.size() - strlen(".sasdata"));
            geomFileName.append(".sasgeom");
        }
    }
    else
    {
        //Is there another way to determine where the data is?
        EXCEPTION1(InvalidDBTypeException, "Cannot parse data with unknown extension.");
    }

    // Figure out of byte swapping is necessary
    int  f = OPEN(geomFileName.c_str(), O_RDONLY | O_BINARY);
    if (f == -1)
    {
        EXCEPTION1(InvalidDBTypeException, "SAS Geometry file is missing.");
    }

    int header = 0;
    ssize_t res = READ(f, (char *)&header, 4); (void) res;
    CLOSE(f);
    
    bSwapEndian = false;
    if (header == 0x20000000)
    {
        bSwapEndian = true;
    }
    else if (header != 32)
    {
        EXCEPTION1(InvalidDBTypeException, "First four bytes of the file have the wrong value.");
    }

    f = OPEN(dataFileName.c_str(), O_RDONLY | O_BINARY);
    if (f == -1)
        bDataFileIsMissing = true;
    else
        bDataFileIsMissing = false;
    CLOSE(f);

    // Need to fake this if the data is missing
    if (bDataFileIsMissing)
    {
        aTimes.push_back(0.0);
    }
}


// ****************************************************************************
//  Method: avtSASFileFormat destructor
//
//  Programmer: David Bremer
//  Creation:   Mon Jul 30 19:38:14 PDT 2007
//
// ****************************************************************************

avtSASFileFormat::~avtSASFileFormat()
{
    if (aAssemblyTypes)
    {
        delete[] aAssemblyTypes;
        aAssemblyTypes = NULL;
        nAssemblyTypes = 0;
    }

    if (aChannels)
    {
        delete[] aChannels;
        aChannels = NULL;
    }
    size_t ii;
    for (ii = 0; ii < aCachedAssemblies.size(); ii++)
    {
        aCachedAssemblies[ii].grid->Delete();
    }
    aCachedAssemblies.clear();
}


// ****************************************************************************
//  Method: avtSASFileFormat::GetNTimesteps
//
//  Purpose:
//      Tells the rest of the code how many timesteps there are in this file.
//
//  Programmer: dbremer -- generated by xml2avt
//  Creation:   Wed Jul 18 12:38:36 PDT 2007
//
// ****************************************************************************

int
avtSASFileFormat::GetNTimesteps(void)
{
    if (aTimes.size() == 0)
        ReadTimeStepData();

    return (int)aTimes.size();
}


// ****************************************************************************
//  Method: avtSASFileFormat::GetTimes
//
//  Purpose:
//      Returns metadata on the simulation time for each dump
//
//  Programmer: David Bremer
//  Creation:   Mon Jul 30 19:38:14 PDT 2007
//
// ****************************************************************************

void
avtSASFileFormat::GetTimes(std::vector<double> &outTimes)
{
    if (aTimes.size() == 0)
        ReadTimeStepData();

    outTimes = aTimes;
}



// ****************************************************************************
//  Method: avtSASFileFormat::GetCycles
//
//  Purpose:
//      Returns metadata on the simulation cycle for each dump.  The data 
//      doesn't contain cycle metadata, but returning it here has the nice
//      side effect of making the query over time and pick over time do
//      the right thing by default.
//
//  Programmer: David Bremer
//  Creation:   Sep 7, 2007
//
// ****************************************************************************

void
avtSASFileFormat::GetCycles(std::vector<int> &outTimes)
{
    if (aTimes.size() == 0)
        ReadTimeStepData();

    size_t ii;
    outTimes.resize( aTimes.size() );
    for (ii = 0; ii < outTimes.size(); ii++)
    {
        outTimes[ii] = ii;
    }
}




// ****************************************************************************
//  Method: avtSASFileFormat::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: dbremer -- generated by xml2avt
//  Creation:   Wed Jul 18 12:38:36 PDT 2007
//
// ****************************************************************************

void
avtSASFileFormat::FreeUpResources(void)
{
}


// ****************************************************************************
//  Method: avtSASFileFormat::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: dbremer -- generated by xml2avt
//  Creation:   Wed Jul 18 12:38:36 PDT 2007
//
// ****************************************************************************

void
avtSASFileFormat::PopulateDatabaseMetaData(avtDatabaseMetaData *md, int timeState)
{

    // TODO:  This is a lot of work, and it only needed at this stage to read the
    // number of assemblys.  It's not easy to avoid this work, but this could be
    // discovered with a little less work by seeking to the end and reading backwards
    // through the assembly instances until I get to the number of assemblys.
    if (!aAssemblyTypes)
        ReadAssemblyTypes();

    string meshname = "mesh";
    avtMeshType mt = AVT_UNSTRUCTURED_MESH;

    int nblocks = nAssemblys;

    int block_origin = 0;
    int spatial_dimension = 3;
    int topological_dimension = 3;
    double *extents = NULL;

    AddMeshToMetaData(md, meshname, mt, extents, nblocks, block_origin,
                      spatial_dimension, topological_dimension);

    AddScalarVarToMetaData(md, "channel_id",   meshname, AVT_ZONECENT);
    AddScalarVarToMetaData(md, "channel_type", meshname, AVT_ZONECENT);
    AddScalarVarToMetaData(md, "assembly_id",   meshname, AVT_ZONECENT);
    AddScalarVarToMetaData(md, "assembly_type", meshname, AVT_ZONECENT);

    if (!bDataFileIsMissing)
        AddScalarVarToMetaData(md, "temperature", meshname, AVT_ZONECENT);

}


// ****************************************************************************
//  Method: avtSASFileFormat::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: dbremer -- generated by xml2avt
//  Creation:   Wed Jul 18 12:38:36 PDT 2007
//
//  Modifications:
//    David Bremer, Sep 7, 2007
//    Changed file io api to handle large files.
// ****************************************************************************

vtkDataSet *
avtSASFileFormat::GetMesh(int /*timestate*/, int domain, const char * /*meshname*/)
{
    if (!aAssemblyTypes)
        ReadAssemblyTypes();

    size_t ii, jj;
    vtkUnstructuredGrid *grid = NULL;

    for (ii = 0; ii < aCachedAssemblies.size(); ii++)
    {
        if (aCachedAssemblies[ii].iDomain == domain)
        {
            aCachedAssemblies[ii].grid->Register(NULL);
            return aCachedAssemblies[ii].grid;
        }
    }

    grid = vtkUnstructuredGrid::New();
    
    int f = OPEN(geomFileName.c_str(), O_RDONLY | O_BINARY);

    // Size is: fortran header/footer + 80 char title +
    //          fortran header/footer + assembly id, type, channel index offset, and x,y,z offset
    OFF64_T iAssemblyInstanceSize = sizeof(int)*2 + 80 + 
                                    sizeof(int)*2 + sizeof(int)*3 + sizeof(double)*3;

    LSEEK64(f, iAssemblyDiskLoc + domain*iAssemblyInstanceSize + sizeof(int)*3 + 80, SEEK_SET);
 
    int iAssemblyID = ReadInt(f); (void) iAssemblyID;
    int iAssemblyType = ReadInt(f);
    int iChannelOffset = ReadInt(f);

    double pos[3];
    ReadDoubleArray(f, pos, 3);
    CLOSE(f);

    // Find the assembly type
    AssemblyType *pType = NULL;
    for (ii = 0; ii < (size_t)nAssemblyTypes; ii++)
    {
        if (iAssemblyType == aAssemblyTypes[ii].id)
        {
            pType = aAssemblyTypes+ii;
            break;
        }
    }
    if (pType == NULL)
    {
        EXCEPTION1(InvalidDBTypeException, "Error reading the mesh.");
    }

    // Add the points
    vtkPoints *pPoints = vtkPoints::New();
    pPoints->SetNumberOfPoints(pType->nUniquePts * pType->nZVals);

    for (jj = 0; jj < (size_t)pType->nZVals; jj++)
    {
        for (ii = 0; ii < (size_t)pType->nUniquePts; ii++)
        {
            pPoints->SetPoint( jj*pType->nUniquePts + ii, 
                               pos[0]+pType->aUniquePts[ii*2], 
                               pos[1]+pType->aUniquePts[ii*2+1], 
                               pos[2]+pType->aZVals[jj] );
        }
    }
    grid->SetPoints(pPoints);

    // Not really deleting here, just decrementing the reference count
    pPoints->Delete();

    // Add the cells
    vtkIdType pts[8] = {0,0,0,0,0,0,0,0};
    for (ii = 0; ii < (size_t)pType->nChannels; ii++)
    {
        int  iCurrChannelGlobalID = iChannelOffset+pType->aChannelIDs[ii];

        // See if I need to exclude this channel because the data to go 
        // with it is missing.
        if (!bDataFileIsMissing)
        {
            if (!aChannels)
                ReadTimeStepData();

            if (!FindChannel(iCurrChannelGlobalID, NULL, NULL))
                continue;
        }

        for (jj = 0; jj < (size_t)pType->nZVals-1; jj++)
        {
            if (pType->aChannelSizes[ii] == 3)
            {
                pts[0] = pType->aChannelPts[ii*4 + 0] + (jj+1)*pType->nUniquePts;
                pts[1] = pType->aChannelPts[ii*4 + 1] + (jj+1)*pType->nUniquePts;
                pts[2] = pType->aChannelPts[ii*4 + 2] + (jj+1)*pType->nUniquePts;
                pts[3] = pType->aChannelPts[ii*4 + 0] + jj    *pType->nUniquePts;
                pts[4] = pType->aChannelPts[ii*4 + 1] + jj    *pType->nUniquePts;
                pts[5] = pType->aChannelPts[ii*4 + 2] + jj    *pType->nUniquePts;

                grid->InsertNextCell(VTK_WEDGE, 6, pts);
            }
            else  //size == 4
            {
                pts[0] = pType->aChannelPts[ii*4 + 0] + jj    *pType->nUniquePts;
                pts[1] = pType->aChannelPts[ii*4 + 1] + jj    *pType->nUniquePts;
                pts[2] = pType->aChannelPts[ii*4 + 2] + jj    *pType->nUniquePts;
                pts[3] = pType->aChannelPts[ii*4 + 3] + jj    *pType->nUniquePts;
                pts[4] = pType->aChannelPts[ii*4 + 0] + (jj+1)*pType->nUniquePts;
                pts[5] = pType->aChannelPts[ii*4 + 1] + (jj+1)*pType->nUniquePts;
                pts[6] = pType->aChannelPts[ii*4 + 2] + (jj+1)*pType->nUniquePts;
                pts[7] = pType->aChannelPts[ii*4 + 3] + (jj+1)*pType->nUniquePts;

                grid->InsertNextCell(VTK_HEXAHEDRON, 8, pts);
            }
        }
    }

    // Cache the grid
    Assembly a;
    a.iDomain = domain;
    a.grid    = grid;
    a.grid->Register(NULL);
    aCachedAssemblies.push_back(a);

    return grid;
}


// ****************************************************************************
//  Method: avtSASFileFormat::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: dbremer -- generated by xml2avt
//  Creation:   Wed Jul 18 12:38:36 PDT 2007
//
//  Modifications:
//    David Bremer, Sep 7, 2007
//    Changed file io api to handle large files.
// ****************************************************************************

vtkDataArray *
avtSASFileFormat::GetVar(int timestate, int domain, const char *varname)
{
    if (strcmp(varname, "temperature")   != 0 && 
        strcmp(varname, "channel_id")    != 0 && 
        strcmp(varname, "channel_type")  != 0 && 
        strcmp(varname, "assembly_id")   != 0 && 
        strcmp(varname, "assembly_type") != 0) 
        EXCEPTION1(InvalidVariableException, varname);

    if (!aAssemblyTypes)
        ReadAssemblyTypes();

    vtkFloatArray *rv = vtkFloatArray::New();

    // Read the assembly type
    // TODO:  Maybe read and cache the assembly type and channel offset for each assembly
    int f = OPEN(geomFileName.c_str(), O_RDONLY | O_BINARY);

    // Size is: fortran header/footer + 80 char title +
    //          fortran header/footer + assembly id, type, channel index offset, and x,y,z offset
    OFF64_T iAssemblyInstanceSize = sizeof(int)*2 + 80 + 
                                    sizeof(int)*2 + sizeof(int)*3 + sizeof(double)*3;
    LSEEK64(f, iAssemblyDiskLoc + domain*iAssemblyInstanceSize + sizeof(int)*3 + 80, SEEK_SET);

    int iAssemblyID    = ReadInt(f);
    int iAssemblyType  = ReadInt(f);
    int iChannelOffset = ReadInt(f);

    CLOSE(f);

    // Find the assembly type
    AssemblyType *pType = NULL;
    int ii, jj;
    for (ii = 0; ii < nAssemblyTypes; ii++)
    {
        if (iAssemblyType == aAssemblyTypes[ii].id)
        {
            pType = aAssemblyTypes+ii;
            break;
        }
    }
    if (pType == NULL)
    {
        EXCEPTION1(InvalidDBTypeException, "Error finding the assembly type");
    }

    bool bReadingTemp = strcmp(varname, "temperature") == 0;

    // Read in the channels
    if (bReadingTemp)
        f = OPEN(dataFileName.c_str(), O_RDONLY | O_BINARY);

    OFF64_T iTimeOffset = (OFF64_T)timestate * (OFF64_T)iTimeStepSize;
    double *tmpData = new double[pType->nZVals-1];

    for (ii = 0; ii < pType->nChannels; ii++)
    {
        int iGlobalChannelID = iChannelOffset+pType->aChannelIDs[ii];

        // If the data file is missing, some code below to skip channels does not apply
        if (bDataFileIsMissing)  
        {
            if (strcmp(varname, "channel_id") == 0)
            {
                for (jj = 0; jj < pType->nZVals-1; jj++)
                    rv->InsertNextValue( (float)(iGlobalChannelID % 1000000));
            }
            else if (strcmp(varname, "channel_type") == 0)
            {
                for (jj = 0; jj < pType->nZVals-1; jj++)
                    rv->InsertNextValue( (float)(iGlobalChannelID / 1000000) );
            }
            else if (strcmp(varname, "assembly_id") == 0)
            {
                for (jj = 0; jj < pType->nZVals-1; jj++)
                    rv->InsertNextValue( (float)iAssemblyID );
            }
            else if (strcmp(varname, "assembly_type") == 0)
            {
                for (jj = 0; jj < pType->nZVals-1; jj++)
                    rv->InsertNextValue( (float)iAssemblyType );
            }
            continue;
        }

        int iChannelLen, iChannelPos;
        //If data for a channel is missing, it's okay to continue.  The corresponding piece of
        //the mesh has also been left out.
        if (!FindChannel(iGlobalChannelID, &iChannelLen, &iChannelPos))
            continue;

        if (iChannelLen != pType->nZVals-1)
        {
            char msg[256];
            sprintf(msg, "Mismatch between the size of channel %d, len=%d, and the size of channels in assembly type %d, len=%d",
                    iGlobalChannelID, iChannelLen, pType->id, pType->nZVals-1);

            EXCEPTION1(InvalidDBTypeException, msg);
        }
        if (bReadingTemp)
        {
            LSEEK64(f, iTimeOffset+iChannelPos, SEEK_SET );
            ReadDoubleArray(f, tmpData, iChannelLen);
    
            // TODO:  See if this is very slow--calling malloc every time or something
            for (jj = 0; jj < iChannelLen; jj++)
                rv->InsertNextValue( (float)tmpData[jj] );
        }
        else
        {
            if (strcmp(varname, "channel_id") == 0)
            {
                for (jj = 0; jj < pType->nZVals-1; jj++)
                    rv->InsertNextValue( (float)(iGlobalChannelID % 1000000));
            }
            else if (strcmp(varname, "channel_type") == 0)
            {
                for (jj = 0; jj < pType->nZVals-1; jj++)
                    rv->InsertNextValue( (float)(iGlobalChannelID / 1000000) );
            }
            else if (strcmp(varname, "assembly_id") == 0)
            {
                for (jj = 0; jj < pType->nZVals-1; jj++)
                    rv->InsertNextValue( (float)iAssemblyID );
            }
            else if (strcmp(varname, "assembly_type") == 0)
            {
                for (jj = 0; jj < pType->nZVals-1; jj++)
                    rv->InsertNextValue( (float)iAssemblyType );
            }
        }
    }

    if (bReadingTemp)
        CLOSE(f);
    delete[] tmpData;
    return rv;
}


// ****************************************************************************
//  Method: avtSASFileFormat::GetVectorVar
//
//  Purpose:
//      Not used, since SAS doesn't support vector variables.
//
//  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: dbremer -- generated by xml2avt
//  Creation:   Wed Jul 18 12:38:36 PDT 2007
//
// ****************************************************************************

vtkDataArray  *
avtSASFileFormat::GetVectorVar(int, int, const char *varname)
{
    EXCEPTION1(InvalidVariableException, varname);
    return NULL;
}



// ****************************************************************************
//  Method: avtSASFileFormat::ReadAssemblyTypes
//
//  Purpose:
//      Parses the first section of the geometry file, which contains the 
//      templates for the assembly types.  The xy points are merged in this
//      routine as well.
//
//  Programmer: David Bremer
//  Creation:   Mon Jul 30 19:38:14 PDT 2007
//
//  Modifications:
//    David Bremer, Sep 7, 2007
//    Changed file io api to handle large files.
// ****************************************************************************

void
avtSASFileFormat::ReadAssemblyTypes()
{
    int f = OPEN(geomFileName.c_str(), O_RDONLY | O_BINARY);

    // Read the header, make sure it's okay.
    ReadInt(f);
    int majorver, minorver, ii, jj, kk, mm;
    char fileid[5], filetype[5];
    fileid[4]   = 0;
    filetype[4] = 0;
    ssize_t res = 0; (void) res;
    res = READ(f, fileid,   4);
    res = READ(f, filetype, 4);
    if (strcmp(fileid, "SAS ") != 0  ||  strcmp(filetype, "GEOM") != 0)
    {
        EXCEPTION1(InvalidDBTypeException, "This file is not a SAS geometry file.");
    }
    majorver = ReadInt(f);
    minorver = ReadInt(f);
    if (majorver != 1 || minorver != 0)
    {
        EXCEPTION1(InvalidDBTypeException, "This reader supports only SAS version 1.0 files.");
    }
    // Seek past two 8-char strings for dump date and time, a fortran footer, 
    // a fortran header, an 80-char title, and a fortran footer
    LSEEK64(f, 8+8+sizeof(int)+sizeof(int)+80+sizeof(int), SEEK_CUR );

    // Read the assembly types
    ReadInt(f);
    nAssemblyTypes = ReadInt(f);
    ReadInt(f);
    aAssemblyTypes = new AssemblyType[nAssemblyTypes];

    for (ii = 0; ii < nAssemblyTypes; ii++)
    {
        // Skip the title and header on the next line
        LSEEK64(f, sizeof(int)+80+sizeof(int)+sizeof(int), SEEK_CUR );

        // Read metadata for this assembly type
        aAssemblyTypes[ii].id        = ReadInt(f);
        aAssemblyTypes[ii].nChannels = ReadInt(f);
        aAssemblyTypes[ii].nZVals    = ReadInt(f);
        aAssemblyTypes[ii].aChannelIDs = new int[aAssemblyTypes[ii].nChannels];
        aAssemblyTypes[ii].aZVals      = new double[aAssemblyTypes[ii].nZVals];
        aAssemblyTypes[ii].aChannelSizes = new int[aAssemblyTypes[ii].nChannels];
        aAssemblyTypes[ii].aChannelPts   = new int[aAssemblyTypes[ii].nChannels * 4];

        ReadDoubleArray(f, aAssemblyTypes[ii].aZVals, aAssemblyTypes[ii].nZVals);
        ReadInt(f);

        double *aPts = new double[aAssemblyTypes[ii].nChannels * 4 * 2];
        double *curr = aPts;

        // Read the raw channel data
        for (jj = 0; jj < aAssemblyTypes[ii].nChannels; jj++)
        {
            ReadInt(f);
            aAssemblyTypes[ii].aChannelIDs[jj]   = ReadInt(f);
            aAssemblyTypes[ii].aChannelSizes[jj] = ReadInt(f);

            ReadDoubleArray(f, curr, aAssemblyTypes[ii].aChannelSizes[jj] * 2);
            curr += aAssemblyTypes[ii].aChannelSizes[jj] * 2;
            ReadInt(f);
        }

        int nPts = (curr - aPts) / 2;

        // Find epsilon        
        double minx = FLT_MAX, miny = FLT_MAX, maxx = -FLT_MAX, maxy = -FLT_MAX;
        for (jj = 0; jj < nPts; jj+=2)
        {
            if (aPts[jj*2] < minx)
                minx = aPts[jj*2];
            if (aPts[jj*2] > maxx)
                maxx = aPts[jj*2];

            if (aPts[jj*2+1] < miny)
                miny = aPts[jj*2+1];
            if (aPts[jj*2+1] > maxy)
                maxy = aPts[jj*2+1];
        }
        double eps2 = ((maxx - minx)*(maxx - minx) + (maxy - miny)*(maxy - miny)) / 100000000.0;

        // Create a list of unique points, and indices into the list
        double *aTmpUniquePts = new double[nPts*2];
        int nUniquePts = 0;
        
        curr = aPts;
        for (jj = 0; jj < aAssemblyTypes[ii].nChannels; jj++)
        {
            // This just sets the 4th slot to -1 instead of garbage, in the cases of wedge-shaped channels
            aAssemblyTypes[ii].aChannelPts[ jj*4 + 3 ] = -1;  

            for (kk = 0; kk < aAssemblyTypes[ii].aChannelSizes[jj]; kk++, curr+=2)
            {
                // Search for this channel's kk-th point in the unique point list.
                // Add it if necessary, then store the index.
                for (mm = 0; mm < nUniquePts; mm++)
                {
                    double dist2 = (curr[0]-aTmpUniquePts[mm*2])   * (curr[0]-aTmpUniquePts[mm*2]) +
                                   (curr[1]-aTmpUniquePts[mm*2+1]) * (curr[1]-aTmpUniquePts[mm*2+1]);
                    if (dist2 < eps2)
                        break;
                }
                if (mm == nUniquePts)
                {
                    aTmpUniquePts[mm*2]   = curr[0];
                    aTmpUniquePts[mm*2+1] = curr[1];
                    nUniquePts++;
                }
                // mm is now the index in the unique point list where curr point was 
                // either found or inserted.  
                aAssemblyTypes[ii].aChannelPts[ jj*4 + kk ] = mm;
            }
        }

        aAssemblyTypes[ii].nUniquePts = nUniquePts;
        aAssemblyTypes[ii].aUniquePts = new double[nUniquePts*2];

        memcpy(aAssemblyTypes[ii].aUniquePts, aTmpUniquePts, sizeof(double)*nUniquePts*2);

        delete[] aPts;
        delete[] aTmpUniquePts;
    }
    
    // Finally, read the number of subassemblys, and record the location on disk of the first one.
    ReadInt(f);
    nAssemblys = ReadInt(f);
    ReadInt(f);

    iAssemblyDiskLoc = LSEEK64(f, 0, SEEK_CUR);

    CLOSE(f);
}


// ****************************************************************************
//  Method: avtSASFileFormat::ReadTimeStepData
//
//  Purpose:
//      Reads some of the data file, to get time information as well as a list
//      of the channel ids and their locations, which are cached and used later
//      in GetVar to locate the data.
//
//  Programmer: David Bremer
//  Creation:   Mon Jul 30 19:38:14 PDT 2007
//
//  Modifications:
//    David Bremer, Sep 7, 2007
//    Changed file io api to handle large files.
//    Changed to allow reads of files that had a truncated write, just 
//    issuing a warning when a problem is detected.
// ****************************************************************************

void
avtSASFileFormat::ReadTimeStepData()
{
    int f = OPEN(dataFileName.c_str(), O_RDONLY | O_BINARY);
    
    OFF64_T end = LSEEK64(f, 0, SEEK_END);
    LSEEK64(f, 0, SEEK_SET);

    ReadInt(f);
    int majorver, minorver, ii;
    char fileid[5], filetype[5];
    fileid[4]   = 0;
    filetype[4] = 0;
    ssize_t res = 0; (void) res;
    res = READ(f, fileid,   4);
    res = READ(f, filetype, 4);
    if (strcmp(fileid, "SAS ") != 0  ||  strcmp(filetype, "DATA") != 0)
    {
        EXCEPTION1(InvalidDBTypeException, "This file is not a SAS data file.");
    }
    majorver = ReadInt(f);
    minorver = ReadInt(f);
    if (majorver != 1 || minorver != 0)
    {
        EXCEPTION1(InvalidDBTypeException, "This reader supports only SAS version 1.0 files.");
    }

    // Skip date/time strings, footer, and two lines of title data
    OFF64_T startTimesteps = LSEEK64(f, 8+8+4+(sizeof(int)*2+80) * 2, SEEK_CUR );
    iTimeStepSize = 0;

    int header = ReadInt(f);
    if (header != 12)
    {
        EXCEPTION1(InvalidDBTypeException, "Error reading SAS data file.");
    }
    double time;
    ReadDoubleArray(f, &time, 1);
    aTimes.push_back(time);

    // Read the first timestep to determine the size of each timestep, 
    // position and number of channels, etc.
    iTimeStepSize = sizeof(int)*3 + sizeof(double); //size of the time/num channels line

    nChannels = ReadInt(f);
    aChannels = new int[nChannels*3];

    // Read the channel metadata--id, numvals, offset--and skip the field data 
    ReadInt(f);
    for (ii = 0; ii < nChannels; ii++)
    {
        ReadInt(f);
        aChannels[ii*3]   = ReadInt(f);      // global channel id
        aChannels[ii*3+1] = ReadInt(f);      // num vals in channel
        aChannels[ii*3+2] = (int)LSEEK64(f, 0, SEEK_CUR);  // disk location of this channel

        LSEEK64(f, sizeof(double)*aChannels[ii*3+1] + sizeof(int), SEEK_CUR );

        iTimeStepSize += sizeof(int)*4 + sizeof(double)*aChannels[ii*3+1];
    }

    // Find out if the data is sorted and/or sequential, to speed searches later.
    bool bChannelsSorted = true, bChannelsSequential = true;
    for (ii = 0; ii < nChannels-1; ii++)
    {
        int delta = aChannels[ii*3 + 3] - aChannels[ii*3];
        if (delta <= 0)
        {
            bChannelsSorted = false;
            bChannelsSequential = false;
            break;
        }
        if (delta != 1)
            bChannelsSequential = false;
    }
    if (bChannelsSequential)
    {
        eChannelOrder = SEQUENTIAL_CHANNELS;
        iFirstChannel = aChannels[0];
    }
    else if (bChannelsSorted)
    {
        eChannelOrder = SORTED_CHANNELS;
    }
    else
    {
        eChannelOrder = UNSORTED_CHANNELS;
    }

    OFF64_T iTotalTimestepSize = end - startTimesteps;

    OFF64_T nTimesteps = iTotalTimestepSize / iTimeStepSize;

    if (iTotalTimestepSize % iTimeStepSize != 0)
    {
        std::string  msg = dataFileName + " has extra bytes at the end, indicating a truncated write";
        avtCallback::IssueWarning(msg.c_str());
    }

    // Seek through the data, pulling out simulation times.
    for (ii = 0; ii < nTimesteps-1; ii++)
    {
        ReadInt(f);
        ReadDoubleArray(f, &time, 1);
        aTimes.push_back(time);

        LSEEK64(f, iTimeStepSize - (sizeof(int)+sizeof(double)), SEEK_CUR );
    }
}


// ****************************************************************************
//  Method: avtSASFileFormat::FindChannel
//
//  Purpose:
//      Searches the cached channel data to find out if a channel present, and
//      if so, where it is located in the file.  Uses direct indexing, a binary
//      search, or a linear search, depending on how well the data is sorted.
//
//  Programmer: David Bremer
//  Creation:   Mon Jul 30 19:38:14 PDT 2007
//
// ****************************************************************************

bool
avtSASFileFormat::FindChannel(int globalChannelID, int *length, int *fileoffset)
{
    int jj;
    if (eChannelOrder == UNSORTED_CHANNELS)
    {
        for (jj = 0; jj < nChannels; jj++)
        {
            if (aChannels[jj*3] == globalChannelID)
            {
                if (length)
                    *length = aChannels[jj*3 + 1];
                if (fileoffset)
                    *fileoffset = aChannels[jj*3 + 2];
                return true;
            }
        }
        return false;
    }
    else if (eChannelOrder == SEQUENTIAL_CHANNELS)
    {
        if (globalChannelID >= iFirstChannel && globalChannelID < iFirstChannel+nChannels)
        {
            if (length)
                *length = aChannels[(globalChannelID-iFirstChannel)*3 + 1];
            if (fileoffset)
                *fileoffset = aChannels[(globalChannelID-iFirstChannel)*3 + 2];
            return true;
        }
        else
            return false;
    }
    else if (eChannelOrder == SORTED_CHANNELS)
    {
        int min = 0, max = nChannels-1;
        int mid = (max+min)/2;
        while (min <= max)
        {
            if (aChannels[mid*3] == globalChannelID)
            {
                if (length)
                    *length = aChannels[mid*3 + 1];
                if (fileoffset)
                    *fileoffset = aChannels[mid*3 + 2];
                return true;
            }
            else if (aChannels[mid*3] > globalChannelID)
            {
                max = mid-1;
                mid = (max+min)/2;
            }
            else
            {
                min = mid+1;
                mid = (max+min)/2;
            }
        }
        return false;
    }
    return false;
}


// ****************************************************************************
//  Method: avtSASFileFormat::ReadInt
//
//  Purpose:
//      Reads and, if necessary, byte-swaps an int 
//
//  Programmer: David Bremer
//  Creation:   Mon Jul 30 19:38:14 PDT 2007
//
//  Modifications:
//    David Bremer, Sep 7, 2007
//    Changed file io api to handle large files.
// ****************************************************************************

int
avtSASFileFormat::ReadInt(int f) 
{
    int tmp;
    ssize_t res = READ(f, (char *)&tmp, sizeof(int) ); (void) res;

    if (bSwapEndian)
        ByteSwap32(&tmp, 1);

    return tmp;
}


// ****************************************************************************
//  Method: avtSASFileFormat::ReadDoubleArray
//
//  Purpose:
//      Reads and, if necessary, byte-swaps an array of doubles 
//
//  Programmer: David Bremer
//  Creation:   Mon Jul 30 19:38:14 PDT 2007
//
//  Modifications:
//    David Bremer, Sep 7, 2007
//    Changed file io api to handle large files.
// ****************************************************************************

void          
avtSASFileFormat::ReadDoubleArray(int f, double *array, int num) 
{
    ssize_t res = READ(f, (char *)array, sizeof(double)*num ); (void) res;

    if (bSwapEndian)
        ByteSwap64(array, num);
}


// ****************************************************************************
//  Method: avtSASFileFormat::ReadFortranString
//
//  Purpose:
//      Reads a fortran string, stripping out the fortran header and footer.
//
//  Programmer: David Bremer
//  Creation:   Mon Jul 30 19:38:14 PDT 2007
//
//  Modifications:
//    David Bremer, Sep 7, 2007
//    Changed file io api to handle large files.
// ****************************************************************************

string     
avtSASFileFormat::ReadFortranString(int f) 
{
    string tmp;
    int len = 0;
    ssize_t res = READ(f, (char *)&len, sizeof(int) ); (void) res;
    if (bSwapEndian)
        ByteSwap32(&len, 1);

    char *t = new char[len+1];
    res = READ(f, t, len);
    t[len] = 0;
    
    //erase trailing spaces
    for (char *p = t+len-1; p >= t; p--)
    {
        if (*p == ' ')
            *p = 0;
    }

    tmp = t;
    delete[] t;
    res = READ(f, (char *)&len, sizeof(int) );

    return tmp;
}


// ****************************************************************************
//  Method: avtSASFileFormat::ByteSwap32
//
//  Purpose:
//      Byte-swap an array of 32-bit values
//
//  Programmer: David Bremer
//  Creation:   Mon Jul 30 19:38:14 PDT 2007
//
// ****************************************************************************

void
avtSASFileFormat::ByteSwap32(void *aVals, int nVals)
{
    char *v = (char *)aVals;
    char tmp;
    for (int ii = 0 ; ii < nVals ; ii++, v+=4)
    {
        tmp = v[0]; v[0] = v[3]; v[3] = tmp;
        tmp = v[1]; v[1] = v[2]; v[2] = tmp;
    }
}


// ****************************************************************************
//  Method: avtSASFileFormat::ByteSwap64
//
//  Purpose:
//      Byte-swap an array of 64-bit values
//
//  Programmer: David Bremer
//  Creation:   Mon Jul 30 19:38:14 PDT 2007
//
// ****************************************************************************

void
avtSASFileFormat::ByteSwap64(void *aVals, int nVals)
{
    char *v = (char *)aVals;
    char tmp;
    for (int ii = 0 ; ii < nVals ; ii++, v+=8)
    {
        tmp = v[0]; v[0] = v[7]; v[7] = tmp;
        tmp = v[1]; v[1] = v[6]; v[6] = tmp;
        tmp = v[2]; v[2] = v[5]; v[5] = tmp;
        tmp = v[3]; v[3] = v[4]; v[4] = tmp;
    }
}





// ****************************************************************************
//  Method: AssemblyType::AssemblyType
//
//  Purpose:
//      constructor
//
//  Programmer: David Bremer
//  Creation:   Mon Jul 30 19:38:14 PDT 2007
//
// ****************************************************************************

AssemblyType::AssemblyType()
{
    id = 0;
    nChannels = 0;
    aChannelIDs = NULL;
    nZVals = 0;
    aZVals = NULL;

    nUniquePts = 0;
    aUniquePts    = NULL;
    aChannelSizes = NULL;
    aChannelPts   = NULL;
}


// ****************************************************************************
//  Method: AssemblyType::~AssemblyType
//
//  Purpose:
//      destructor
//
//  Programmer: David Bremer
//  Creation:   Mon Jul 30 19:38:14 PDT 2007
//
// ****************************************************************************

AssemblyType::~AssemblyType()
{
    if (aChannelIDs)
        delete[] aChannelIDs;
    if (aZVals)
        delete[] aZVals;
    if (aUniquePts)
        delete[] aUniquePts;
    if (aChannelSizes)
        delete[] aChannelSizes;
    if (aChannelPts)
        delete[] aChannelPts;
}




