#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iniparser.h>
#include "parmt_utils.h"
#include "prepmt/prepmt_dataArchive.h"
#include "prepmt/prepmt_greens.h"
#include "prepmt/prepmt_hudson96.h"
#include "prepmt/prepmt_commands.h"
#ifdef PARMT_USE_INTEL
#include <mkl_cblas.h>
#else
#include <cblas.h>
#endif
#include "ispl/process.h"
#include "iscl/array/array.h"
#include "iscl/fft/fft.h"
#include "iscl/memory/memory.h"
#include "iscl/os/os.h"
#include "iscl/signal/convolve.h"
#include "iscl/signal/filter.h"

static int getPrimaryArrival(const struct sacHeader_struct hdr,
                             double *time, char phaseName[8]);
static int getPrimaryArrivalNoPolarity(const struct sacHeader_struct hdr,
                                       double *time, char phaseName[8]);
static void shiftGreens(const int npgrns, const int lag,
                        double *__restrict__ G, double *__restrict__ work);

//============================================================================//
/*!
 * @brief Converts the fundamental faults Green's functions to Green's
 *        functions that can be used by parmt.
 *
 * @param[in] nobs      Number of observations.
 * @param[in] obs       Observed waveforms to contextualize.  This is an
 *                      array of length [nobs].
 * @param[in] ndepth    Number of depths.
 * @param[in] ntstar    Number of t*s'.
 * @param[in] ffGrns    Fundamental fault Green's functions for every t* and
 *                      depth in the grid search for each observation.  This
 *                      is an array of dimension [nobs x ndepth x ntstar x 10].
 *
 * @param[in,out] grns  Holds space for Green's functions.
 *                      Contains the Green's functions that can be applied to
 *                      a moment tensor to produce a synthetic for every t*
 *                      and depth in the grid search for each observation.
 *                      This is an array of length [nobs x ndepth x ntstar x 6].
 *
 * @result 0 indicates success.
 *
 * @author Ben Baker, ISTI
 *
 */
int prepmt_greens_ffGreensToGreens(const int nobs,
                                   const struct sacData_struct *obs,
                                   const int ndepth,
                                   const int ntstar,
                                   const struct sacData_struct *ffGrns,
                                   struct sacData_struct *grns)
{
    char knetwk[8], kstnm[8], kcmpnm[8], khole[8], phaseName[8],
         phaseNameGrns[8];
    double az, baz, cmpaz, cmpinc, cmpincSEED, epoch, epochNew,
           evla, evlo, o, pick, pickTime, pickTimeGrns, stel, stla, stlo;
    int indices[6], i, icomp, id, ierr, idx, iobs, it, kndx, l, npts;
    const char *kcmpnms[6] = {"GXX\0", "GYY\0", "GZZ\0",
                              "GXY\0", "GXZ\0", "GYZ\0"};
/*
    const double xmom = 1.0;     // no confusing `relative' magnitudes
    const double xcps = 1.e-20;  // convert dyne-cm mt to output cm
    const double cm2m = 1.e-2;   // cm to meters
    const double dcm2nm = 1.e+7; // magnitudes intended to be specified in
                                 // Dyne-cm but I work in N-m
    // Given a M0 in Newton-meters get a seismogram in meters
    const double xscal = xmom*xcps*cm2m*dcm2nm;
*/
    const int nTimeVars = 11;
    const enum sacHeader_enum pickVars[11]
       = {SAC_FLOAT_A,
          SAC_FLOAT_T0, SAC_FLOAT_T1, SAC_FLOAT_T2, SAC_FLOAT_T3,
          SAC_FLOAT_T4, SAC_FLOAT_T5, SAC_FLOAT_T6, SAC_FLOAT_T7,
          SAC_FLOAT_T8, SAC_FLOAT_T9};
    //memset(grns, 0, sizeof(struct tdSearchGreens_struct));
    for (iobs=0; iobs<nobs; iobs++)
    {
        ierr = 0;
        ierr += sacio_getFloatHeader(SAC_FLOAT_AZ,
                                     obs[iobs].header, &az);
        ierr += sacio_getFloatHeader(SAC_FLOAT_BAZ,
                                     obs[iobs].header, &baz);
        ierr += sacio_getFloatHeader(SAC_FLOAT_CMPINC,
                                     obs[iobs].header, &cmpinc);
        ierr += sacio_getFloatHeader(SAC_FLOAT_CMPAZ,
                                     obs[iobs].header, &cmpaz);
        ierr += sacio_getFloatHeader(SAC_FLOAT_EVLA,
                                     obs[iobs].header, &evla);
        ierr += sacio_getFloatHeader(SAC_FLOAT_EVLO,
                                     obs[iobs].header, &evlo);
        ierr += sacio_getFloatHeader(SAC_FLOAT_STLA,
                                     obs[iobs].header, &stla);
        ierr += sacio_getFloatHeader(SAC_FLOAT_STLO,
                                     obs[iobs].header, &stlo);
        ierr += sacio_getCharacterHeader(SAC_CHAR_KNETWK,
                                         obs[iobs].header, knetwk);
        ierr += sacio_getCharacterHeader(SAC_CHAR_KSTNM,
                                         obs[iobs].header, kstnm);
        ierr += sacio_getCharacterHeader(SAC_CHAR_KCMPNM,
                                         obs[iobs].header, kcmpnm);
        if (ierr != 0)
        {
            fprintf(stderr, "%s: Error reading header variables\n", __func__);
            break;
        }
        // This one isn't critical but it would be nice to have
        sacio_getFloatHeader(SAC_FLOAT_STEL,
                             obs[iobs].header, &stel);
        // Station location code is not terribly important
        sacio_getCharacterHeader(SAC_CHAR_KHOLE, obs[iobs].header, khole);
        cmpincSEED = cmpinc - 90.0; // SAC to SEED convention
        // Get the primary arrival
        ierr = sacio_getEpochalStartTime(obs[iobs].header, &epoch);
        if (ierr != 0)
        {
            fprintf(stderr, "%s: Error getting start time\n", __func__);
            break;
        }
        ierr += getPrimaryArrivalNoPolarity(obs[iobs].header, &pickTime,
                                            phaseName);
        //ierr += getPrimaryArrival(obs[iobs].header, &pickTime, phaseName);
        if (ierr != 0)
        {
            fprintf(stderr, "%s: Error getting primary pick\n", __func__);
            break;
        }
        // Need to figure out the component
        ierr = parmt_utils_getComponent(kcmpnm, cmpinc, &icomp);
        if (ierr != 0)
        {
            fprintf(stderr, "%s: Error getting component\n", __func__);
            break;
        }
/*
        icomp = 1;
        if (fabs(cmpinc - 0.0) < 1.e-4 || fabs(cmpinc - 180.0) < 1.e-4)
        {
            if (kcmpnm[2] == 'Z' || kcmpnm[2] == 'z' || kcmpnm[2] == '1')
            {
                icomp = 1;
            }
            else
            {
                fprintf(stderr, "%s: cmpinc is 0 but channel=%s isn't vertical",
                        __func__, kcmpnm);
            }
        }
        else if (fabs(cmpinc - 90.0) < 1.e-4)
        {
            icomp = 2;
            if (kcmpnm[2] == 'N' || kcmpnm[2] == 'n' ||
                kcmpnm[2] == '1' || kcmpnm[2] == '2')
            {
                icomp = 2;
            }
            else if (kcmpnm[2] == 'N' || kcmpnm[2] == 'n' ||
                     kcmpnm[2] == '2' || kcmpnm[2] == '3')
            {
                icomp = 3;
            }
            else
            {
                fprintf(stderr, "%s: cmpinc is 90 but channel=%s is weird",
                        __func__, kcmpnm);
                return -1;
            }
        }
*/
        /*
        else if (kcmpnm[2] == 'E' || kcmpnm[2] == 'e' || kcmpnm[2] == '3')
        {
            icomp = 3;
        }
        */
/*
        else
        {
            fprintf(stderr, "%s: Can't classify component %s with cmpinc=%f\n",
                    __func__, kcmpnm, cmpinc);
        }
*/
        // Process all Green's functions in this block
        for (id=0; id<ndepth; id++)
        {
            for (it=0; it<ntstar; it++)
            {
                idx = prepmt_hudson96_observationDepthTstarToIndex(
                             ndepth, ntstar, iobs, id, it);
                kndx = 10*idx;
                sacio_getFloatHeader(SAC_FLOAT_O, ffGrns[kndx].header, &o);
                getPrimaryArrival(ffGrns[kndx].header,
                                  &pickTimeGrns, phaseNameGrns);
                if (strcasecmp(phaseNameGrns, phaseName) != 0)
                {
                    fprintf(stdout, "%s: Phase name mismatch %s %s\n",
                            __func__, phaseName, phaseNameGrns);
                }
                npts = ffGrns[kndx].npts;
                ierr = prepmt_greens_getHudson96GreensFunctionsIndices(
                                 nobs, ntstar, ndepth,
                                 iobs, it, id,
                                 indices);
                /*
                indx = prepmt_greens_getHudson96GreensFunctionIndex(G11_GRNS,
                                                            nobs, ntstar, ndepth,
                                                            iobs, it, id);
                */
                for (i=0; i<6; i++)
                {
                    sacio_copy(ffGrns[kndx], &grns[indices[i]]);
                    if (obs[iobs].pz.lhavePZ)
                    {
                        sacio_copyPolesAndZeros(obs[iobs].pz,
                                                &grns[indices[i]].pz);
                    }
                    sacio_setFloatHeader(SAC_FLOAT_AZ, az,
                                         &grns[indices[i]].header);
                    sacio_setFloatHeader(SAC_FLOAT_BAZ, baz,
                                         &grns[indices[i]].header);
                    sacio_setFloatHeader(SAC_FLOAT_CMPAZ, cmpaz,
                                         &grns[indices[i]].header);
                    sacio_setFloatHeader(SAC_FLOAT_CMPINC, cmpinc,
                                         &grns[indices[i]].header);
                    sacio_setFloatHeader(SAC_FLOAT_EVLA, evla,
                                         &grns[indices[i]].header);
                    sacio_setFloatHeader(SAC_FLOAT_EVLO, evlo,
                                         &grns[indices[i]].header);
                    sacio_setFloatHeader(SAC_FLOAT_STLA, stla,
                                         &grns[indices[i]].header);
                    sacio_setFloatHeader(SAC_FLOAT_STLO, stlo,
                                         &grns[indices[i]].header);
                    sacio_setFloatHeader(SAC_FLOAT_STEL, stel,
                                         &grns[indices[i]].header);
                    sacio_setCharacterHeader(SAC_CHAR_KNETWK, knetwk,
                                             &grns[indices[i]].header);
                    sacio_setCharacterHeader(SAC_CHAR_KSTNM, kstnm,
                                             &grns[indices[i]].header);
                    sacio_setCharacterHeader(SAC_CHAR_KHOLE, khole,
                                             &grns[indices[i]].header);
                    sacio_setCharacterHeader(SAC_CHAR_KCMPNM, kcmpnms[i],
                                             &grns[indices[i]].header);
                    sacio_setCharacterHeader(SAC_CHAR_KEVNM, "SYNTHETIC\0",
                                             &grns[indices[i]].header);
                    // Set the start time by aligning on the arrival
                    epochNew = epoch + (pickTime - o) - pickTimeGrns;
                    sacio_setEpochalStartTime(epochNew,
                                              &grns[indices[i]].header);
                    // Update the pick times
                    for (l=0; l<nTimeVars; l++)
                    {
                        ierr = sacio_getFloatHeader(pickVars[l],
                                                    grns[indices[i]].header,
                                                    &pick);
                        if (ierr == 0)
                        {
                            pick = pick + o;
                            sacio_setFloatHeader(pickVars[l],
                                                 pick,
                                                 &grns[indices[i]].header);
                        }
                    }
                }
                //printf("%d %d\n", kndx, indices[0]);
                //printf("%e\n", array_max64f(npts, ffGrns[kndx+0].data));
                ierr = parmt_utils_ff2mtGreens64f(npts, icomp,
                                                  az, baz,
                                                  cmpaz, cmpincSEED,
                                                  ffGrns[kndx+0].data,
                                                  ffGrns[kndx+1].data,
                                                  ffGrns[kndx+2].data,
                                                  ffGrns[kndx+3].data,
                                                  ffGrns[kndx+4].data,
                                                  ffGrns[kndx+5].data,
                                                  ffGrns[kndx+6].data,
                                                  ffGrns[kndx+7].data,
                                                  ffGrns[kndx+8].data,
                                                  ffGrns[kndx+9].data,
                                                  grns[indices[0]].data,
                                                  grns[indices[1]].data,
                                                  grns[indices[2]].data,
                                                  grns[indices[3]].data,
                                                  grns[indices[4]].data,
                                                  grns[indices[5]].data);
                if (ierr != 0)
                {
                    fprintf(stderr, "%s: Failed to rotate Greens functions\n",
                            __func__);
                }
                // Fix the characteristic magnitude scaling in CPS
                // N.B. this is done early now
                /*
                for (i=0; i<6; i++)
                {
                    cblas_dscal(npts, xscal, grns[indices[i]].data, 1);
                    //printf("%d %d %e\n", kndx, indices[i],
                    //          array_max64f(npts,grns[indices[i]].data));
                }
                */
            }
        }
    }
    return 0;
}
//============================================================================//
/*!
 * @brief Convenience function which returns the index of the Green's
 *        function on the Green's function structure.
 *
 * @param[in] GMT_TERM    Name of the desired Green's function:
 *                        (G11_TERM, G22_TERM, ..., G23_TERM).
 * @param[in] nobs        Number of observations.
 * @param[in] ntstar      Number of t*'s.
 * @param[in] ndepth      Number of depths.
 * @param[in] iobs        Desired observation number (C numbering).
 * @param[in] itstar      Desired t* (C numbering).
 * @param[in] idepth      Desired depth (C numbering).
 *
 * @result Negative indicates failure.  Otherwise, this is the index in
 *         grns.grns corresponding to the desired
 *         (iobs, idepth, itstar, G??_GRNS) coordinate.
 *
 * @author Ben Baker, ISTI
 *
 */
int prepmt_greens_getHudson96GreensFunctionIndex(
    const enum prepmtGreens_enum GMT_TERM,
    const int nobs, const int ntstar, const int ndepth,
    const int iobs, const int itstar, const int idepth)
{
    int igx, indx, ngrns;
    ngrns = 6*nobs*ntstar*ndepth;
    indx =-1;
    igx = (int) GMT_TERM - 1;
    if (igx < 0 || igx > 5)
    {
        fprintf(stderr, "%s: Can't classify Green's functions index\n",
                __func__);
        return indx;
    }
    indx = iobs*(6*ntstar*ndepth)
         + idepth*(6*ntstar)
         + itstar*6
         + igx;
    if (indx < 0 || indx >= ngrns)
    {
        fprintf(stderr, "%s: indx out of bounds - segfault is coming\n",
                __func__);
        return -1;
    }
    return indx;
}
//============================================================================//
/*!
 * @brief Repicks the Green's functions onset time with an STA/LTA picker.
 *
 * @param[in] sta        Short term average window length (seconds).
 * @param[in] lta        Long term average window length (seconds).
 * @param[in] threshPct  Percentage of max STA/LTA after which an arrival
 *                       is declared.
 * @param[in] nobs       Number of observations.
 * @param[in] ntstar     Number of t*'s.
 * @param[in] ndepth     Number of depths.
 * @param[in] iobs       C numbered observation index.
 * @param[in] itstar     C numbered t* index.
 * @param[in] idepth     C numbered depth index.
 *
 * @param[in,out] grns   On input contains the Green's functions.
 *                       On output the first defined arrival time has been
 *                       modified with an STA/LTA picker.  The header variable
 *                       to be modified is most likely SAC_FLOAT_A.
 *                       This is an array of dimension [6].
 *
 * @brief 0 indicates success.
 *
 * @author Ben Baker, ISTI
 *
 */
int prepmt_greens_repickGreensWithSTALTA(
    const double sta, const double lta, const double threshPct,
    struct sacData_struct *grns)
{
    struct stalta_struct stalta;
    enum sacHeader_enum pickHeader;
    double *charFn, *g, *Gxx, *Gyy, *Gzz, *Gxy, *Gxz, *Gyz,
           *gxxPad, *gyyPad, *gzzPad, *gxyPad, *gxzPad, *gyzPad,
           apick, charMax, dt, tpick;
    int ierr, k, npts, nlta, npad, nsta, nwork, prePad;
    const int nTimeVars = 11;
    const enum sacHeader_enum pickVars[11]
       = {SAC_FLOAT_A,
          SAC_FLOAT_T0, SAC_FLOAT_T1, SAC_FLOAT_T2, SAC_FLOAT_T3,
          SAC_FLOAT_T4, SAC_FLOAT_T5, SAC_FLOAT_T6, SAC_FLOAT_T7,
          SAC_FLOAT_T8, SAC_FLOAT_T9};
    // Check STA/LTA
    ierr = 0;
    memset(&stalta, 0, sizeof(struct stalta_struct));
    if (lta < sta || sta < 0.0)
    {
        if (lta < sta){fprintf(stderr, "%s: Error lta < sta\n", __func__);}
        if (sta < 0.0){fprintf(stderr, "%s: Error sta is < 0\n", __func__);}
        return -1;
    }
    ierr = sacio_getIntegerHeader(SAC_INT_NPTS,
                                  grns[0].header, &npts);
    if (ierr != 0 || npts < 1)
    {
        if (ierr != 0)
        {
            fprintf(stderr, "%s: Error getting number of points from header\n",
                    __func__);
        }
        else
        {
            fprintf(stderr, "%s: Error no data points\n", __func__);
        }
        return -1;
    }
    ierr = sacio_getFloatHeader(SAC_FLOAT_DELTA,
                                grns[0].header, &dt);
    if (ierr != 0 || dt <= 0.0)
    {
        if (ierr != 0){fprintf(stderr, "%s: failed to get dt\n", __func__);}
        if (dt <= 0.0)
        {
            fprintf(stderr, "%s: invalid sampling period\n", __func__);
        }
        return -1;
    }
    // Define the windows
    nsta = (int) (sta/dt + 0.5);
    nlta = (int) (lta/dt + 0.5);
    prePad = MAX(64, fft_nextpow2(nlta, &ierr));
    npad = prePad + npts;
    // Set space
    gxxPad = memory_calloc64f(npad);
    gyyPad = memory_calloc64f(npad);
    gzzPad = memory_calloc64f(npad);
    gxyPad = memory_calloc64f(npad);
    gxzPad = memory_calloc64f(npad);
    gyzPad = memory_calloc64f(npad);
    charFn = memory_calloc64f(npad);
    // Reference pointers
    Gxx = grns[0].data;
    Gyy = grns[1].data;
    Gzz = grns[2].data;
    Gxy = grns[3].data;
    Gxz = grns[4].data;
    Gyz = grns[5].data;
    // Pre-pad signals
    array_set64f_work(prePad, Gxx[0], gxxPad);
    array_set64f_work(prePad, Gyy[0], gyyPad);
    array_set64f_work(prePad, Gzz[0], gzzPad);
    array_set64f_work(prePad, Gxy[0], gxyPad);
    array_set64f_work(prePad, Gxz[0], gxzPad);
    array_set64f_work(prePad, Gyz[0], gyzPad);
    // Copy rest of array
    array_copy64f_work(npts, Gxx, &gxxPad[prePad]);
    array_copy64f_work(npts, Gyy, &gyyPad[prePad]);
    array_copy64f_work(npts, Gzz, &gzzPad[prePad]);
    array_copy64f_work(npts, Gxy, &gxyPad[prePad]);
    array_copy64f_work(npts, Gxz, &gxzPad[prePad]);
    array_copy64f_work(npts, Gyz, &gyzPad[prePad]);
    // apply the sta/lta
    for (k=0; k<6; k++)
    {
        g = NULL;
        if (k == 0)
        {
            g = gxxPad;
        }
        else if (k == 1)
        {
            g = gyyPad;
        }
        else if (k == 2)
        {
            g = gzzPad;
        }
        else if (k == 3)
        {
            g = gxyPad;
        }
        else if (k == 4)
        {
            g = gxzPad;
        }
        else if (k == 5)
        {
            g = gyzPad;
        }
        ierr = stalta_setShortAndLongTermAverage(nsta, nlta, &stalta);
        if (ierr != 0)
        {
            fprintf(stderr, "%s: Error setting STA/LTA\n", __func__);
            break;
        }
        ierr = stalta_setData64f(npad, g, &stalta);
        if (ierr != 0)
        {
            fprintf(stderr, "%s: Error setting data\n", __func__);
            break;
        }
        ierr = stalta_applySTALTA(&stalta);
        if (ierr != 0)
        {
            fprintf(stderr, "%s: Error applying STA/LTA\n", __func__);
            break;
        }
        ierr = stalta_getData64f(stalta, npad, &nwork, g);
        if (ierr != 0)
        {
            fprintf(stderr, "%s: Error getting result\n", __func__);
            break;
        }
        cblas_daxpy(npad, 1.0, g, 1, charFn, 1);
        stalta_resetInitialConditions(&stalta);
        stalta_resetFinalConditions(&stalta);
        g = NULL;
    }
    // Compute the pick time
    charMax = array_max64f(npts, &charFn[prePad], &ierr);
    tpick =-1.0;
    for (k=prePad; k<npad; k++)
    {
        if (charFn[k] > 0.01*threshPct*charMax)
        {
            tpick = (double) (k - prePad)*dt;
            break;
        }
    }
    if (tpick ==-1.0)
    {
        tpick = (double) (array_argmax64f(npad, charFn, &ierr) - prePad)*dt;
    }
    // Overwrite the pick
    pickHeader = SAC_UNKNOWN_HDRVAR;
    for (k=0; k<nTimeVars; k++)
    {
        if (sacio_getFloatHeader(pickVars[k], grns[k].header, &apick) == 0)
        {
            pickHeader = pickVars[k];
            break;
         }
    }
    if (pickHeader == SAC_UNKNOWN_HDRVAR)
    {
        fprintf(stdout, "%s: Could not locate primary arrival - assume A\n",
                __func__);
        pickHeader = SAC_FLOAT_A;
    }
//printf("%f %f\n", tpick, apick);
    //double apick;
    //sacio_getFloatHeader(SAC_FLOAT_A, grns[0].header, &apick);
    for (k=0; k<6; k++)
    {
        sacio_setFloatHeader(pickHeader, tpick,
                             &grns[k].header);
    }
    // Dereference pointers and free space
    Gxx = NULL;
    Gyy = NULL;
    Gzz = NULL;
    Gxy = NULL;
    Gxz = NULL;
    Gyz = NULL;
    memory_free64f(&gxxPad);
    memory_free64f(&gyyPad);
    memory_free64f(&gzzPad);
    memory_free64f(&gxyPad);
    memory_free64f(&gxzPad);
    memory_free64f(&gyzPad);
    memory_free64f(&charFn);
    stalta_free(&stalta);
    return ierr;
}
//============================================================================//
int prepmt_greens_processHudson96Greens(
    const int nobs, const int ntstar, const int ndepth,
    const struct prepmtCommands_struct cmds,
    struct sacData_struct *grns)
{
    struct serialCommands_struct commands;
    struct parallelCommands_struct parallelCommands;
    double *G, dt, dt0, epoch, epoch0, time;
    int *dataPtr, indices[6],
        i, i0, i1, i2, ierr, iobs, idep, it, kndx, l, npts, npts0, nq,
        nwork, ny, ns, nsuse;
    bool lnewDt, lnewStartTime;
    const int nTimeVars = 12;
    const enum sacHeader_enum timeVars[12]
       = {SAC_FLOAT_A, SAC_FLOAT_O,
          SAC_FLOAT_T0, SAC_FLOAT_T1, SAC_FLOAT_T2, SAC_FLOAT_T3,
          SAC_FLOAT_T4, SAC_FLOAT_T5, SAC_FLOAT_T6, SAC_FLOAT_T7,
          SAC_FLOAT_T8, SAC_FLOAT_T9};
    const int spaceInquiry =-1;
    // Loop on the observations
    ns = ndepth*ntstar*6;
    dataPtr = memory_calloc32i(ns);
    for (iobs=0; iobs<nobs; iobs++)
    {
        // Set the processing structure
        memset(&parallelCommands, 0,
               sizeof(struct parallelCommands_struct));
               memset(&commands, 0, sizeof(struct serialCommands_struct));
        kndx = prepmt_greens_getHudson96GreensFunctionIndex(G11_GRNS,
                                                      nobs, ntstar, ndepth,
                                                      iobs, 0, 0);
        // Parse the commands
        ierr = process_stringsToSerialCommandsOptions(
                                      cmds.cmds[iobs].ncmds,
                                      (const char **) cmds.cmds[iobs].cmds,
                                      &commands);
        if (ierr != 0)
        {
            fprintf(stderr, "%s: Error setting serial command string\n",
                    __func__);
            goto ERROR;
        }
        // Determine some characteristics of the processing
        sacio_getEpochalStartTime(grns[kndx].header, &epoch0);
        sacio_getFloatHeader(SAC_FLOAT_DELTA, grns[kndx].header, &dt0);
        lnewDt = false;
        lnewStartTime = false;
        epoch = epoch0;
        dt = dt0;
        for (i=0; i<commands.ncmds; i++)
        {
            if (commands.commands[i].type == CUT_COMMAND)
            {
                i0 = commands.commands[i].cut.i0;
                epoch = epoch + (double) i0*dt;
                lnewStartTime = true;
            }
            if (commands.commands[i].type == DOWNSAMPLE_COMMAND)
            {
                nq = commands.commands[i].downsample.nq;
                dt = dt*(double) nq;
                lnewDt = true;
            }
            if (commands.commands[i].type == DECIMATE_COMMAND)
            {
                nq = commands.commands[i].decimate.nqAll;
                dt = dt*(double) nq;
                lnewDt = true;
            }
        }
        // Set the commands on the parallel processing structure
        ierr = process_setCommandOnAllParallelCommands(ns, commands,
                                                       &parallelCommands);
        if (ierr != 0)
        {
            fprintf(stderr, "%s: Error setting the parallel commands\n",
                    __func__);
            goto ERROR;
        }
        // Get the data
        dataPtr[0] = 0;
        for (idep=0; idep<ndepth; idep++)
        {
            for (it=0; it<ntstar; it++)
            {
                kndx = prepmt_greens_getHudson96GreensFunctionIndex(G11_GRNS,
                                                          nobs, ntstar, ndepth,
                                                          iobs, idep, it);
                ierr = sacio_getIntegerHeader(SAC_INT_NPTS,
                                              grns[kndx].header, &npts);
                if (ierr != 0)
                {
                    fprintf(stderr, "%s: Error getting npts\n", __func__);
                    goto ERROR;
                }
                i1 = idep*6*ntstar + it*6 + 0;
                i2 = i1 + 6;
                for (i=i1; i<i2; i++)
                {
                    dataPtr[i+1] = dataPtr[i] + npts;
                }
            }
        }
        nwork = dataPtr[6*ndepth*ntstar];
        if (nwork < 1)
        {
            fprintf(stderr, "%s: Invalid workspace size: %d\n",
                    __func__, nwork);
            ierr = 1;
            goto ERROR;
        }
        G = memory_calloc64f(nwork);
        for (idep=0; idep<ndepth; idep++)
        {
            for (it=0; it<ntstar; it++)
            {
                ierr = prepmt_greens_getHudson96GreensFunctionsIndices(
                                        nobs, ntstar, ndepth,
                                        iobs, it, idep, indices);
                if (ierr != 0)
                {
                    fprintf(stderr, "%s: Error getting index\n", __func__);
                    goto ERROR;
                }
                for (i=0; i<6; i++)
                {
                    i1 = idep*6*ntstar + it*6 + i;
                    //if (i == 0){printf("fill: %d %d %d %e\n", i1, indices[i], dataPtr[i1], array_max64f(grns[indices[i]].npts, grns[indices[i]].data));}

                    cblas_dcopy(grns[indices[i]].npts,
                                grns[indices[i]].data, 1, &G[dataPtr[i1]], 1);
                }
            }
        }
        // Set the data
        ierr =  process_setParallelCommandsData64f(ns, dataPtr,
                                                   G, &parallelCommands);
        if (ierr != 0)
        {
            fprintf(stderr, "%s: Error setting data\n", __func__);
            goto ERROR;
        }
        // Apply the commands
        ierr = process_applyParallelCommands(&parallelCommands);
        if (ierr != 0)
        {
            fprintf(stderr, "%s: Error processing data\n", __func__);
            goto ERROR;
        }
        // Get the data
        ierr = process_getParallelCommandsData64f(parallelCommands,
                                                  spaceInquiry, spaceInquiry,
                                                  &ny, &nsuse,
                                                  dataPtr, G);
        if (ny < nwork)
        {
            memory_free64f(&G);
            G = memory_calloc64f(ny);
        }
        ny = nwork;
        ierr = process_getParallelCommandsData64f(parallelCommands,
                                                  nwork, ns,
                                                  &ny, &nsuse, dataPtr, G);
        //printf("%d\n", nsuse);
        if (ierr != 0)
        {
            fprintf(stderr, "%s: Error getting data\n", __func__);
            goto ERROR;
        }
        // Unpack the data
        for (idep=0; idep<ndepth; idep++)
        {
            for (it=0; it<ntstar; it++)
            {
                ierr = prepmt_greens_getHudson96GreensFunctionsIndices(
                                        nobs, ntstar, ndepth,
                                        iobs, it, idep, indices);
                if (ierr != 0)
                {
                    fprintf(stderr, "%s: Error getting index\n", __func__);
                    goto ERROR;
                }
                for (i=0; i<6; i++)
                {
                    i1 = idep*6*ntstar + it*6 + i;
                    sacio_getIntegerHeader(SAC_INT_NPTS,
                                           grns[indices[i]].header, &npts0);
                    npts = dataPtr[i1+1] - dataPtr[i1];
                    // Resize event
                    if (npts != npts0)
                    {
                        sacio_freeData(&grns[indices[i]]);
                        grns[indices[i]].data = sacio_malloc64f(npts);
                        grns[indices[i]].npts = npts;
                        sacio_setIntegerHeader(SAC_INT_NPTS, npts,
                                               &grns[indices[i]].header);
                        ierr = array_copy64f_work(npts,
                                                  &G[dataPtr[i1]],
                                                  grns[indices[i]].data);
                    }
                    else
                    {
                        ierr = array_copy64f_work(npts,
                                                  &G[dataPtr[i1]],
                                                  grns[indices[i]].data);
                        //if (i == 0){printf("%d %d %e\n", iobs, indices[i], array_max64f(grns[indices[i]].npts, grns[indices[i]].data));}
                    }
                }
                // Update the times
                if (lnewStartTime)
                {
                    for (i=0; i<6; i++)
                    {
                        // Update the picks
                        for (l=0; l<nTimeVars; l++)
                        {
                            ierr = sacio_getFloatHeader(timeVars[l],
                                          grns[indices[i]].header, &time);
                            if (ierr == 0)
                            {
                                time = time + epoch0; // Turn to real time
                                time = time - epoch;  // Relative to new time
                                sacio_setFloatHeader(timeVars[l], time,
                                                    &grns[indices[i]].header);
                            }
                        } // Loop on picks
                        sacio_setEpochalStartTime(epoch,
                                                  &grns[indices[i]].header);
                    } // Loop on signals
                }
                // Update the sampling period
                if (lnewDt)
                {
                    for (i=0; i<6; i++)
                    {
                        sacio_setFloatHeader(SAC_FLOAT_DELTA, dt,
                                             &grns[indices[i]].header);
                    }
                }
            } // Loop on t*
        } // Loop on depths
        process_freeSerialCommands(&commands);
        process_freeParallelCommands(&parallelCommands);
        memory_free64f(&G);
    }
ERROR:;
    memory_free32i(&dataPtr);
    return 0;
}
//============================================================================//
/*!
 * @brief Writes a Green's functions archive that is appropriate for parmt.
 *
 * @param[in] archiveName   Name of archive file to write.
 * @param[in] nwaves        Number of waveforms.
 * @param[in] ndepths       Number of depths in grid-search.
 * @param[in] evla          Event latitude (degrees).
 * @param[in] evlo          Event longitude (degrees).
 * @param[in] depths        Depths (km) in grid search.  This is an array of
 *                          dimension [ndepths].
 * @param[in] sac           SAC observations to write.  This is an array of
 *                          dimension [nwaves].
 * @param[in] sacGrns       Corresponding Green's functions to write.  This
 *                          is an array of dimension [6 x ndepths x nwaves]
 *                          where the fastest dimension is 6 and the slowest
 *                          dimension is nwaves.  Each Green's function
 *                          group for the waveform, depth pair is packed:
 *                          \f$ \{ G_{xx}, G_{yy}, G_{zz},
 *                                 G_{xy}, G_{xz}, G_{yz} \} \f$.
 *
 * @result 0 indicates success.
 *
 * @author Ben Baker
 *
 */
int prepmt_greens_writeArchive(const char *archiveName, //const char *archiveDir, const char *projnm,
                               const int nwaves, const int ndepths,
                               const double evla, const double evlo,
                               const double *__restrict__ depths,
                               const struct sacData_struct *sac,
                               const struct sacData_struct *sacGrns)
{
    int id, ierr, indx, k;
    hid_t h5fl;
    // initialize the archive
    ierr = prepmt_dataArchive_createArchive(archiveName, //archiveDir, projnm,
                                            ndepths, evla, evlo, depths);
    if (ierr != 0)
    {
         fprintf(stderr, "%s: Error creating archive\n", __func__);
         return -1;
    }
    // open it for writing
    h5fl = prepmt_dataArchive_openArchive(archiveName, &ierr); //archiveDir, projnm, &ierr);
    // write each observation and greens fns
    for (k=0; k<nwaves; k++)
    {
        ierr = prepmt_dataArchive_addObservation(h5fl, sac[k]);
        for (id=0; id<ndepths; id++)
        {
            indx = k*ndepths*6 + id*6;
            ierr = prepmt_dataArchive_addGreensFunctions(h5fl, sac[k],
                                              sacGrns[indx+0], sacGrns[indx+1],
                                              sacGrns[indx+2], sacGrns[indx+3],
                                              sacGrns[indx+4], sacGrns[indx+5]);
            //printf("%f %e\n", sacGrns[indx+0].header.evdp, sacGrns[indx+0].data[12]);
            if (ierr != 0){return -1;}
        }
    }
    prepmt_dataArchive_closeArchive(h5fl);
    return ierr;
}
//============================================================================//
int prepmt_greens_cutHudson96FromData(const int nobs,
                                      const struct sacData_struct *data,
                                      const int ndepth, const int ntstar,
                                      struct sacData_struct *grns)
{
    char **newCmds;
    struct prepmtModifyCommands_struct options;
    double cut0, dt, epoch, epochData;
    int i, ierr, idep, indx, iobs, it, npts;
    const int ncmds = 2;
    //const char *cmds[2] = {"cut\0", "demean\0"};
    struct prepmtCommands_struct prepMTcmds;
    newCmds = (char **) calloc(2, sizeof(char *));
    for (i=0; i<2; i++)
    {
        newCmds[i] = (char *) calloc(MAX_CMD_LEN, sizeof(char));
    }
    strcpy(newCmds[1], "demean\0");

    prepMTcmds.cmds = (struct prepmtCommandsChars_struct *)
                      calloc(1, sizeof(struct prepmtCommandsChars_struct));
    prepMTcmds.cmds[0].ncmds = ncmds;
    memset(&options, 0, sizeof(struct prepmtModifyCommands_struct));
    for (iobs=0; iobs<nobs; iobs++)
    {
        // Get the primary pick
        ierr = sacio_getEpochalStartTime(data[iobs].header, &epoch);
        if (ierr != 0)
        {
            fprintf(stderr, "%s: Error getting start time\n", __func__);
            break;
        }
        epochData = epoch;
        sacio_getFloatHeader(SAC_FLOAT_DELTA, data[iobs].header, &dt);
        sacio_getIntegerHeader(SAC_INT_NPTS, data[iobs].header, &npts);
        cut0 = epoch;
        //cut1 = cut0 + dt*(double) (npts - 1);
        for (idep=0; idep<ndepth; idep++)
        {
            for (it=0; it<ntstar; it++)
            {
                indx = prepmt_greens_getHudson96GreensFunctionIndex(G11_GRNS,
                                                           nobs, ntstar, ndepth,
                                                           iobs, it, idep);
//printf("%d %d %d %d\n", iobs, it, idep, indx);
                sacio_getEpochalStartTime(grns[indx].header, &epoch);
                if (cut0 - epoch < 0.0)
                {
                    fprintf(stdout, "%s: Cut may be funky\n", __func__);
                }
                options.cut0 = fmax(0.0, cut0 - epoch);
                options.cut1 = options.cut0 + dt*(double) (npts - 1);
/*
                newCmds = prepmt_commands_modifyCommands(ncmds, cmds,
                                                         options, grns[indx],
                                                         &ierr);
*/
                ierr = cut_cutEpochalTimesToString(grns[indx].header.delta,
                                                   0.0,
                                                   options.cut0,
                                                   options.cut1,
                                                   newCmds[0]);
                //strcpy(newCmds[1], "demean");
                //printf("%s %f\n", newCmds[0], grns[indx].header.delta);
                prepMTcmds.cmds[0].cmds = newCmds;
                ierr = prepmt_greens_processHudson96Greens(1, 1, 1,
                                                           prepMTcmds,
                                                           &grns[indx]);
                // Enforce the epochal time
                for (i=0; i<6; i++)
                {
                    sacio_setEpochalStartTime(epochData, &grns[indx+i].header);
                }
//printf("%d %e\n", indx, array_max64f(grns[indx].npts, grns[indx].data));
                for (i=0; i<ncmds; i++)
                {
               //     free(newCmds[i]);
                }
                //free(newCmds);
            }
        }
    }
    for (i=0; i<2; i++)
    {
        free(newCmds[i]);
    }
    free(newCmds[i]);
    free(prepMTcmds.cmds);
    return 0;
}
//============================================================================//
/*!
 * @brief Convenience function for extracting the:
 *        \$ \{ G_{xx}, G_{yy}, G_{zz}, G_{xy}, G_{xz}, G_{yz} \} \$
 *        Green's functions indices for the observation, t*, and depth.
 *
 * @param[in] nobs      Total number of observations.
 * @param[in] ntstar    Total number of t*.
 * @param[in] ndepth    Total number of depths.
 * @param[in] iobs      Observation number.  This is C indexed.
 * @param[in] itstar    t* index.  This is C indexed.
 * @param[in] idepth    Depth index.  This is C indexed.
 * @param[in] grns      Contains the Green's functions.
 * @param[out] indices  Contains the Green's functions indices defining
 *                      the indices that return the:
 *                       \$ \{ G_{xx}, G_{yy}, G_{zz},
 *                             G_{xy}, G_{xz}, G_{yz} \} \$
 *                      for this observation, t*, and depth.
 *
 * @result 0 indicates success.
 *
 * @author Ben Baker, ISTI
 *
 */
int prepmt_greens_getHudson96GreensFunctionsIndices(
    const int nobs, const int ntstar, const int ndepth,
    const int iobs, const int itstar, const int idepth,
    int indices[6])
{
    int i, ierr;
    const enum prepmtGreens_enum mtTerm[6] =
       {G11_GRNS, G22_GRNS, G33_GRNS, G12_GRNS, G13_GRNS, G23_GRNS};
    ierr = 0;
    for (i=0; i<6; i++)
    {
        indices[i] = prepmt_greens_getHudson96GreensFunctionIndex(mtTerm[i],
                                                          nobs, ntstar, ndepth,
                                                          iobs, itstar, idepth);
        if (indices[i] < 0){ierr = ierr + 1;}
    }
    if (ierr != 0)
    {
        fprintf(stderr, "%s: Error getting indices (obs,t*,depth)=(%d,%d,%d)\n",
                __func__, iobs, itstar, idepth);
    }
    return ierr;
}
//============================================================================//
/*!
 * @brief Aligns the Green's functions to the data via cross-correlation.
 *
 * @param[in] data          Holds the observed data.
 * @param[in] luseEnvelope  If true then cross-correlate the envelopes of
 *                          the waveforms. \n
 *                          Otherwise, cross-correlate the waveforms
 *                          themselves.  In this case the absolute values
 *                          of the cross-correlation are used.
 * @param[in] lnorm         If true then use a normalized cross-correlation
 *                          so that all waveforms can contribute equally. \n
 *                          Otherwise, use the non-normalized cross-correlation.
 * @param[in] maxTimLag     Max time lag (seconds) allowed in cross-correlation.
 *
 * @param[in,out] grns      On input contains the Green's functions to
 *                          align to the data.
 *                          On output contains the Green's functions which
 *                          have been aligned to the data via cross-correlation.
 *
 * @result 0 indicates success.
 *
 * @author Ben Baker, ISTI
 *
 */
int prepmt_greens_xcAlignGreensToData(const struct sacData_struct data,
                                      const bool luseEnvelope, const bool lnorm,
                                      const double maxTimeLag,
                                      struct sacData_struct *grns)
{
    double *dataPad, *Gxx, *Gyy, *Gzz, *Gxy, *Gxz, *Gyz, *xc,
           dt, dtGrns, epochData, epochGrns;
    int ierr, insertData, lxc, maxShift, npadData, npts, npgrns;
    ierr = 0;
    sacio_getIntegerHeader(SAC_INT_NPTS, data.header, &npts);
    sacio_getIntegerHeader(SAC_INT_NPTS, grns[0].header, &npgrns);
    if (npts < 1 || npgrns < 1)
    {
        fprintf(stderr, "%s: Error no data\n", __func__);
        fprintf(stderr, "%s: Error no grns functions\n", __func__);
        return -1;
    }
    sacio_getFloatHeader(SAC_FLOAT_DELTA, data.header, &dt);
    sacio_getFloatHeader(SAC_FLOAT_DELTA, data.header, &dtGrns);
    if (fabs(dt - dtGrns) > 1.e-6 || dt <= 0.0)
    {
        if (dt <= 0.0){fprintf(stderr, "%s: dt is invalid\n", __func__);}
        if (fabs(dt - dtGrns) > 1.e-6)
        {
            fprintf(stderr, "%s: dt is inconsistent\n", __func__);
        }
        return -1;
    }
    // Need a common reference - choose the start time
    ierr = sacio_getEpochalStartTime(data.header,    &epochData);
    if (ierr != 0)
    {
        fprintf(stderr, "%s: Error getting data start time\n", __func__);
        return -1;
    }
    ierr = sacio_getEpochalStartTime(grns[0].header, &epochGrns);
    if (ierr != 0)
    {
        fprintf(stderr, "%s: Error getting grns start time\n", __func__);
        return -1;
    }
    if (epochData < epochGrns)
    {
        fprintf(stderr, "%s: epochData < epochGrns not programmed\n", __func__);
        return -1;
    }
    // Insert the data in a epochal time aligned array
    insertData = (int) ((epochData - epochGrns)/dt + 0.5);
    npadData = insertData + npts;
    //printf("%d %d\n", insertData, npts);
    dataPad = memory_calloc64f(npadData);
    array_copy64f_work(npts, data.data, &dataPad[insertData]);
    // Create pointers to Green's functions
    Gxx = grns[0].data;
    Gyy = grns[1].data;
    Gzz = grns[2].data;
    Gxy = grns[3].data;
    Gxz = grns[4].data;
    Gyz = grns[5].data;
    // Compute hte max lag
    maxShift =-1;
    if (maxTimeLag >= 0.0){maxShift = (int) (maxTimeLag/dt + 0.5);}
//printf("%d %f %f\n", maxShift, maxTimeLag/dt, maxTimeLag);
    // Align
    lxc = npadData + npgrns - 1; //npts + npgrns - 1;
    xc = memory_calloc64f(lxc);
    ierr = prepmt_greens_xcAlignGreensToData_work(npadData, dataPad, //data.data,
                                                  npgrns, luseEnvelope, lnorm,
                                                  maxShift,
                                                  Gxx, Gyy, Gzz,
                                                  Gxy, Gxz, Gyz,
                                                  lxc, xc);
    memory_free64f(&xc);
    memory_free64f(&dataPad);
    // Dereference pointers
    Gxx = NULL;
    Gyy = NULL;
    Gzz = NULL;
    Gxy = NULL;
    Gxz = NULL;
    Gyz = NULL;
    return ierr;
}
//============================================================================//
int prepmt_greens_xcAlignGreensToData_work(
    const int npts, double *__restrict__ data,
    const int npgrns,
    const bool luseEnvelope, const bool lnorm,
    const int maxShift,
    double *__restrict__ Gxx,
    double *__restrict__ Gyy,
    double *__restrict__ Gzz,
    double *__restrict__ Gxy,
    double *__restrict__ Gxz,
    double *__restrict__ Gyz,
    const int lxc, double *__restrict__ xc)
{
    double *dwork, *Gwork, *xcorrWork, egrns, esig, xdiv;
    int i, ierr, ierr1, kmt, l1, l2, lag, lcref, nlag;
    //------------------------------------------------------------------------//
    ierr = 0;
    lcref = npgrns + npts - 1;
    if (lcref > lxc){return -1;}
    xcorrWork = memory_calloc64f(lcref);
    array_zeros64f_work(lxc, xc);
    if (luseEnvelope)
    {
        Gwork = memory_calloc64f(npgrns);
        dwork = memory_calloc64f(npts);
        ierr = signal_filter_envelope64f_work(npts, data, dwork);
        esig = cblas_dnrm2(npts, dwork, 1);
    }
    else
    {
        Gwork = NULL;
        dwork = data;
        esig = cblas_dnrm2(npts, data, 1);
    }
    // Loop on the Green's functions
    for (kmt=0; kmt<6; kmt++)
    {
        if (kmt == 0)
        {
            if (!luseEnvelope)
            {
                Gwork = Gxx;
            }
            else
            {
                signal_filter_envelope64f_work (npgrns, Gxx, Gwork);
            }
        }
        else if (kmt == 1)
        {
            if (!luseEnvelope)
            {
                Gwork = Gyy;
            }
            else
            {
                signal_filter_envelope64f_work (npgrns, Gyy, Gwork);
            }
        }
        else if (kmt == 2)
        {
            if (!luseEnvelope)
            {
                Gwork = Gzz;
            }
            else
            {
                signal_filter_envelope64f_work (npgrns, Gzz, Gwork);
            }
        }
        else if (kmt == 3)
        {
            if (!luseEnvelope)
            {
                Gwork = Gxy;
            }
            else
            {
                signal_filter_envelope64f_work (npgrns, Gxy, Gwork);
            }
        }
        else if (kmt == 4)
        {
            if (!luseEnvelope)
            {
                Gwork = Gxz;
            }
            else
            {
                signal_filter_envelope64f_work (npgrns, Gxz, Gwork);
            }
        }
        else
        {
            if (!luseEnvelope)
            {
                Gwork = Gyz;
            }
            else
            {
                signal_filter_envelope64f_work (npgrns, Gyz, Gwork);
            }
        }
        egrns = cblas_dnrm2(npgrns, Gwork, 1);
        ierr1 = signal_convolve_correlate64f_work(npts, dwork,
                                                  npgrns,
                                                  Gwork,
                                                  CONVCOR_FULL,
                                                  lcref, xcorrWork);
        if (ierr1 != 0)
        {
            if (ierr1 != 0)
            {
                fprintf(stderr, "%s: Error correlating kmt %d\n",
                        __func__, kmt);
            }
            ierr = ierr + 1;
        }
        // sum in the result - absolute value handles polarity
        else
        {
            xdiv = 1.0;
            if (lnorm){xdiv = 1.0/(esig*egrns);}
            if (luseEnvelope)
            {
                #pragma omp simd
                for (i=0; i<lcref; i++)
                {
                    xc[i] = xc[i] + xcorrWork[i]*xdiv;
                }
            }
            else
            {
                #pragma omp simd
                for (i=0; i<lcref; i++)
                {
                    xc[i] = xc[i] + fabs(xcorrWork[i]*xdiv);
//if (kmt == 5){printf("%e\n", xc[i]);}
                }
            }
        }
    } // Loop on green's functions
    // Compute the lag where the times range from [-npgrns:npts].  Hence,
    // lag = npgrns is the unlagged cross-correlation.
    if (maxShift < 0)
    {
        lag = array_argmax64f(lcref, xc, &ierr);
    }
    else
    {
        l1 = MAX(0, npgrns - maxShift);
        l2 = MIN(lcref - 1, npgrns + maxShift);
        nlag = l2 - l1 + 1;
        lag = l1 + array_argmax64f(nlag, &xc[l1], &ierr);
    }
    lag =-npgrns + lag;
    //printf("%d %d %d\n", lag, npgrns, npts);
/*
for (i=0; i<npts; i++)
{
printf("%e\n", data[i]);
}
for (i=0; i<npgrns; i++)
{
printf("%e %e %e %e %e %e\n", Gxx[i], Gyy[i], Gzz[i], Gxy[i], Gxz[i], Gyz[i]);
}
for (i=0; i<lcref; i++)
{
printf("%d %e\n",-npgrns+i, xc[i]);
}
printf("%d\n", lag);
*/
//getchar();
    // Shift the greens' functions
    array_zeros64f_work(lcref, xcorrWork);
    shiftGreens(npgrns, lag, Gxx, xcorrWork);
    shiftGreens(npgrns, lag, Gyy, xcorrWork);
    shiftGreens(npgrns, lag, Gzz, xcorrWork);
    shiftGreens(npgrns, lag, Gxy, xcorrWork);
    shiftGreens(npgrns, lag, Gxz, xcorrWork);
    shiftGreens(npgrns, lag, Gyz, xcorrWork);
    if (luseEnvelope)
    {
        memory_free64f(&Gwork);
        memory_free64f(&dwork);
    }
    else
    {
        dwork = NULL;
        Gwork = NULL;
    }
    memory_free64f(&xcorrWork);
    return 0;
}
//============================================================================//
/*!
 * @brief Utility function for use after the Green's functions have been
 *        manually aligned to issue a measure of the alignment quality.
 *
 * @param[in] npts          Number of points in data and Green's functions.
 * @param[in] luseEnvelope  If true then cross-correlate the envelopes of
 *                          the waveforms. \n
 *                          Otherwise, cross-correlate the waveforms
 *                          themselves.  In this case the absolute values
 *                          of the cross-correlation are used.
 * @param[in] lnorm         If true then use a normalized cross-correlation
 *                          so that all waveforms can contribute equally. \n
 *                          Otherwise, use the non-normalized cross-correlation.
 * @param[in] data          Observed waveforms.  This is an array of dimension
 *                          [npts].
 * @param[in] Gxx           Aligned Gxx Green's function.  This is an array of
 *                          dimension [npts].
 * @param[in] Gyy           Aligned Gyy Green's function.  This is an array of
 *                          dimension [npts].
 * @param[in] Gzz           Aligned Gzz Green's function.  This is an array of
 *                          dimension [npts].
 * @param[in] Gxy           Aligned Gxy Green's function.  This is an array of
 *                          dimension [npts].
 * @param[in] Gxz           Aligned Gxz Green's function.  This is an array of
 *                          dimension [npts].
 * @param[in] Gyz           Aligned Gyz Green's function.  This is an array of
 *                          dimension [npts].
 *
 * @param[out] ierr         0 indicates success.
 *
 * @result The zero-lag cross-correlation score of the Green's function to
 *         data alignment.
 *
 * @author Ben Baker, ISTI
 *
 */
double prepmt_greens_scoreXCAlignment(const int npts,
                                      const bool luseEnvelope,
                                      const bool lnorm,
                                      const double *__restrict__ data,
                                      const double *__restrict__ Gxx,
                                      const double *__restrict__ Gyy,
                                      const double *__restrict__ Gzz,
                                      const double *__restrict__ Gxy,
                                      const double *__restrict__ Gxz,
                                      const double *__restrict__ Gyz,
                                      int *ierr)
{
    double *dwork, *Gwork, egrns, esig, xcStack, xdiv;
    const double *G;
    int i;
    enum isclError_enum isclError;
    *ierr = 0;
    xcStack = 0.0;
    dwork = NULL;
    G = NULL;
    Gwork = memory_calloc64f(npts);
    if (luseEnvelope)
    {
        dwork = signal_filter_envelope64f(npts, data, &isclError);
        if (isclError != ISCL_SUCCESS)
        {
            fprintf(stderr, "%s: Error computing data envelope\n", __func__);
            *ierr = 1;
            return xcStack;
        }
        esig = cblas_dnrm2(npts, dwork, 1);
    }
    else
    {
        dwork = array_copy64f(npts, data, &isclError);
        if (isclError != ISCL_SUCCESS)
        {
            fprintf(stderr, "%s: Error copying data\n", __func__);
            *ierr = 1;
            return xcStack;
        }
        esig = cblas_dnrm2(npts, data, 1);
    }
    for (i=0; i<6; i++)
    {
        if (i == 0)
        {
            G = Gxx;
        }
        else if (i == 1)
        {
            G = Gyy;
        }
        else if (i == 2)
        {
            G = Gzz;
        }
        else if (i == 3)
        {
            G = Gxy;
        }
        else if (i == 4)
        {
            G = Gxz;
        }
        else if (i == 5)
        {
            G = Gyz;
        }
        if (luseEnvelope)
        {
            isclError = signal_filter_envelope64f_work(npts, G, Gwork);
            if (isclError != ISCL_SUCCESS)
            {
                fprintf(stderr, "%s: Error computing Grns envelope\n",
                        __func__);
                *ierr = 1;
                return xcStack;
            }
        }
        else
        {
            isclError = array_copy64f_work(npts, G, Gwork);
            if (isclError != ISCL_SUCCESS)
            {
                fprintf(stderr, "%s: Error copying G to Gwork\n", __func__);
                *ierr = 1;
                return xcStack;
            }
        }
        egrns = cblas_dnrm2(npts, Gwork, 1);
        xdiv = 1.0;
        if (lnorm){xdiv = 1.0/(esig*egrns);}
        xdiv = xdiv*1.0/6.0; // Normalization term for sum
        if (luseEnvelope)
        {
            xcStack = xcStack + xdiv*cblas_ddot(npts, dwork, 1, Gwork, 1);
        }
        else
        {
            xcStack = xcStack + xdiv*fabs(cblas_ddot(npts, dwork, 1, Gwork, 1));
        }
        G = NULL;
    }
    memory_free64f(&dwork);
    memory_free64f(&Gwork);
    return xcStack;
}
//============================================================================//
static int getPrimaryArrival(const struct sacHeader_struct hdr,
                             double *time, char phaseName[8])
{
    const enum sacHeader_enum timeVars[11]
       = {SAC_FLOAT_A,
          SAC_FLOAT_T0, SAC_FLOAT_T1, SAC_FLOAT_T2, SAC_FLOAT_T3,
          SAC_FLOAT_T4, SAC_FLOAT_T5, SAC_FLOAT_T6, SAC_FLOAT_T7,
          SAC_FLOAT_T8, SAC_FLOAT_T9};
    const enum sacHeader_enum timeVarNames[11]
       = {SAC_CHAR_KA,
          SAC_CHAR_KT0, SAC_CHAR_KT1, SAC_CHAR_KT2, SAC_CHAR_KT3,
          SAC_CHAR_KT4, SAC_CHAR_KT5, SAC_CHAR_KT6, SAC_CHAR_KT7,
          SAC_CHAR_KT8, SAC_CHAR_KT9};
    int i, ifound1, ifound2;
    memset(phaseName, 0, 8*sizeof(char));
    for (i=0; i<11; i++)
    {
        ifound1 = sacio_getFloatHeader(timeVars[i], hdr, time);
        ifound2 = sacio_getCharacterHeader(timeVarNames[i], hdr, phaseName);
        if (ifound1 == 0 && ifound2 == 0){return 0;}
    }
    fprintf(stderr, "%s: Failed to get primary pick\n", __func__);
    *time =-12345.0;
    memset(phaseName, 0, 8*sizeof(char));
    strcpy(phaseName, "-12345");
    return -1;
}
//============================================================================//
static int getPrimaryArrivalNoPolarity(const struct sacHeader_struct hdr,
                                       double *time, char phaseName[8])
{
    int ierr;
    size_t lenos;
    ierr = getPrimaryArrival(hdr, time, phaseName);
    if (ierr == 0)
    {
        lenos = strlen(phaseName);
        if (lenos > 0)
        {
            if (phaseName[lenos-1] == '+' || phaseName[lenos-1] == '-')
            {
                phaseName[lenos-1] = '\0';
            }
        }
    }
    return ierr;
}
//============================================================================//
static void shiftGreens(const int npgrns, const int lag,
                        double *__restrict__ G, double *__restrict__ work)
{
    int ncopy;
    if (lag == 0){return;}
    array_copy64f_work(npgrns, G, work);
    array_zeros64f_work(npgrns, G);
    if (lag > 0)
    {
        ncopy = npgrns - lag;
        array_copy64f_work(ncopy, work, &G[lag]);
    }
    else
    {
        ncopy = npgrns + lag;
        array_copy64f_work(ncopy, &work[-lag], G);
    }
    return;
}


/*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*
**                                                                                **
** This file forms part of the Underworld geophysics modelling application.         **
**                                                                                **
** For full license and copyright information, please refer to the LICENSE.md file  **
** located at the project root, or contact the authors.                             **
**                                                                                **
**~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*/
#include <petsc.h>
#include <petscmat.h>
#include <petscvec.h>
#include <petscksp.h>
#include <petscpc.h>

#include "common-driver-utils.h"
#include "pc_GtKG.h"
#include "pc_ScaledGtKG.h"

PetscErrorCode BSSCR_PetscExtStokesSolversInitialize( void )
{
    Stg_PCRegister( "gtkg", "Solvers/KSPSolvers/src/BSSCR", "BSSCR_PCCreate_GtKG", BSSCR_PCCreate_GtKG );
	//Stg_PCRegister( "bfbt", "Solvers/KSPSolvers/src/BSSCR", "BSSCR_PCCreate_GtKG", BSSCR_PCCreate_GtKG );
	PetscFunctionReturn(0);
}

PetscErrorCode BSSCR_PetscExtStokesSolversFinalize( void )
{

#if ((PETSC_VERSION_MAJOR==3) && (PETSC_VERSION_MINOR>=4))
//  PCFinalizePackage();
#else
//  PCRegisterDestroy();
#endif

	PetscFunctionReturn(0);
}


/* $Id$      */
/*--------------------------------------------------------------------*/
/*;  Copyright (C) 2013                                               */
/*;  Associated Universities, Inc. Washington DC, USA.                */
/*;                                                                   */
/*;  This program is free software; you can redistribute it and/or    */
/*;  modify it under the terms of the GNU General Public License as   */
/*;  published by the Free Software Foundation; either version 2 of   */
/*;  the License, or (at your option) any later version.              */
/*;                                                                   */
/*;  This program is distributed in the hope that it will be useful,  */
/*;  but WITHOUT ANY WARRANTY; without even the implied warranty of   */
/*;  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    */
/*;  GNU General Public License for more details.                     */
/*;                                                                   */
/*;  You should have received a copy of the GNU General Public        */
/*;  License along with this program; if not, write to the Free       */
/*;  Software Foundation, Inc., 675 Massachusetts Ave, Cambridge,     */
/*;  MA 02139, USA.                                                   */
/*;                                                                   */
/*;Correspondence about this software should be addressed as follows: */
/*;         Internet email: bcotton@nrao.edu.                         */
/*;         Postal address: William Cotton                            */
/*;                         National Radio Astronomy Observatory      */
/*;                         520 Edgemont Road                         */
/*;                         Charlottesville, VA 22903-2475 USA        */
/*--------------------------------------------------------------------*/
#ifndef OBITRMFIT_H
#define OBITRMFIT_H

#include "Obit.h"
#include "ObitErr.h"
#include "ObitImage.h"
#include "ObitBeamShape.h"
#include "ObitThread.h"
#include "ObitInfoList.h"
#ifdef HAVE_GSL
#include <gsl/gsl_multifit_nlin.h>
#endif /* HAVE_GSL */

/*-------- Obit:  Merx mollis mortibus nuper ------------------*/
/**
 * \file ObitRMFit.h
 *
 * ObitRMFit Class for fitting rotatioin measures (RM) to Stokes Q and U image pixels
 *
 * This class does least squares fitting of RM and EVPA at 0 lambda^2
 * Either an image cube or a set of single plane images at arbitrary
 * lambda squares may be fitted.
 * The result is an image cube of RM and  EVPA at 0 lambda^2 as the planes.
 * The function ObitRMFitEval will evaluate this fit and return an image
 * with the flux densities at the desired frequencies.
 *
 * \section ObitRMFitaccess Creators and Destructors
 * An ObitRMFit will usually be created using ObitRMFitCreate which allows
 * specifying a name for the object as well as other information.
 *
 * A copy of a pointer to an ObitRMFit should always be made using the
 * #ObitRMFitRef function which updates the reference count in the object.
 * Then whenever freeing an ObitRMFit or changing a pointer, the function
 * #ObitRMFitUnref will decrement the reference count and destroy the object
 * when the reference count hits 0.
 * There is no explicit destructor.
 */

/*--------------Class definitions-------------------------------------*/
/** ObitRMFit Class structure. */
typedef struct {
#include "ObitRMFitDef.h"   /* this class definition */
} ObitRMFit;

/*----------------- Macroes ---------------------------*/
/**
 * Macro to unreference (and possibly destroy) an ObitRMFit
 * returns a ObitRMFit*.
 * in = object to unreference
 */
#define ObitRMFitUnref(in) ObitUnref (in)

/**
 * Macro to reference (update reference count) an ObitRMFit.
 * returns a ObitRMFit*.
 * in = object to reference
 */
#define ObitRMFitRef(in) ObitRef (in)

/**
 * Macro to determine if an object is the member of this or a
 * derived class.
 * Returns TRUE if a member, else FALSE
 * in = object to reference
 */
#define ObitRMFitIsA(in) ObitIsA (in, ObitRMFitGetClass())

/*---------------Public functions---------------------------*/
/** Public: Class initializer. */
void ObitRMFitClassInit (void);

/** Public: Default Constructor. */
ObitRMFit* newObitRMFit (gchar* name);

/** Public: Create/initialize ObitRMFit structures */
ObitRMFit* ObitRMFitCreate (gchar* name, olong nterm);
/** Typedef for definition of class pointer structure */
typedef ObitRMFit* (*ObitRMFitCreateFP) (gchar* name,
						     olong nterm);

/** Public: ClassInfo pointer */
gconstpointer ObitRMFitGetClass (void);

/** Public: Copy (deep) constructor. */
ObitRMFit* ObitRMFitCopy  (ObitRMFit *in, ObitRMFit *out, ObitErr *err);

/** Public: Copy structure. */
void ObitRMFitClone (ObitRMFit *in, ObitRMFit *out, ObitErr *err);

/** Public: Fit spectrum to an image cube */
void ObitRMFitCube (ObitRMFit* in, ObitImage *inQImage, ObitImage *inUImage,
		    ObitImage *outImage, ObitErr *err);
/** Typedef for definition of class pointer structure */
typedef void(*ObitRMFitCubeFP) (ObitRMFit* in,
				ObitImage *inQImage, ObitImage *inUImage,
				ObitImage *outImage, ObitErr *err);

/** Public: Fit spectrum to an array of images */
void ObitRMFitImArr (ObitRMFit* in, olong nimage,
		     ObitImage **imQArr, ObitImage **imUArr,
		     ObitImage *outImage, ObitErr *err);
/** Typedef for definition of class pointer structure */
typedef void(*ObitRMFitImArrFP) (ObitRMFit* in, olong nimage,
				 ObitImage **imQArr, ObitImage **imUArr,
				 ObitImage *outImage, ObitErr *err);

/** Public: Fit single spectrum */
ofloat* ObitRMFitSingle (olong nlamb2, olong nterm, odouble refLamb2, odouble *lamb2,
			 ofloat *qflux, ofloat *qsigma, ofloat *uflux, ofloat *usigma,
			 ObitErr *err);
/** Typedef for definition of class pointer structure */
typedef ofloat*(*ObitRMFitSingleFP) (olong nlamb2, olong nterm, odouble refLamb2, odouble *lamb2,
				     ofloat *qflux, ofloat *qsigma, ofloat *uflux, ofloat *usigma,
				     ObitErr *err);

/** Public: Make fitting arg structure */
gpointer ObitRMFitMakeArg (olong nlamb2, olong nterm,
			   odouble refLamb2, odouble *lamb2,
			   ofloat **out, ObitErr *err);

/** Public: Fit single RM using arg */
void ObitRMFitSingleArg (gpointer arg,
			 ofloat *qflux, ofloat *qsigma,
			 ofloat *uflux, ofloat *usigma,
			 ofloat *out);

/** Public: Kill fitting arg structure */
void ObitRMFitKillArg (gpointer arg);
/*----------- ClassInfo Structure -----------------------------------*/
/**
 * ClassInfo Structure.
 * Contains class name, a pointer to any parent class
 * (NULL if none) and function pointers.
 */
typedef struct  {
#include "ObitRMFitClassDef.h"
} ObitRMFitClassInfo;

#endif /* OBITFRMFIT_H */


#include "tools.h"

#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>

gsl_rng **_random = NULL;

double tool_get_time()
{
    struct timeval t;
    gettimeofday(&t, NULL);
    return (double)t.tv_sec + (double)t.tv_usec * 1e-6;
}

double tool_cut(double x)
{
    if (x <= 0.0)
        return 0.0;
    if (x >= 1.0)
        return 1.0;
    return x;
}

void print_array(double c[], int l)
{
    for (int i = 0; i < l - 1; i++)
    {
        printf("%.3f ", c[i]);
    }
    printf("%.3f\n", c[l - 1]);
}

void print_array_b(bool c[], int l)
{
    for (int i = 0; i < l - 1; i++)
    {
        printf("%d,", c[i] ? 1 : 0);
    }
    printf("%d\n", c[l - 1] ? 1 : 0);
}

void print_array_i(int c[], int l)
{
    for (int i = 0; i < l - 1; i++)
    {
        printf("%d,", c[i]);
    }
    printf("%d\n", c[l - 1]);
}

void print_array_ui(unsigned int c[], int l)
{
    for (int i = 0; i < l - 1; i++)
    {
        printf("%u,", c[i]);
    }
    printf("%u\n", c[l - 1]);
}

void print_array_2d(bool **c, int xlen, int ylen)
{
    for (int x = 0; x < xlen; x++)
    {
        for (int y = 0; y < ylen; y++)
        {
            printf("%d,", c[x][y]);
        }
        puts("");
    }
}

void print_array_2d_d(double **c, int xlen, int ylen)
{
    for (int x = 0; x < xlen; x++)
    {
        for (int y = 0; y < ylen; y++)
        {
            printf("%.2f,", c[x][y]);
        }
        puts("");
    }
}

void tool_free_array_d(double **a, int xlen)
{
    for (int x = 0; x < xlen; x++)
    {
        free(a[x]);
    }
    free(a);
}

void tool_free_array_b(bool **a, int xlen)
{
    for (int x = 0; x < xlen; x++)
    {
        free(a[x]);
    }
    free(a);
}

void tool_free_array_ui(unsigned int **a, int xlen)
{
    for (int x = 0; x < xlen; x++)
    {
        free(a[x]);
    }
    free(a);
}

bool **tool_alloc_array_b(int xlen, int ylen)
{

    bool **a = (bool **)malloc(xlen * sizeof(bool *));
    for (int x = 0; x < xlen; x++)
    {
        a[x] = (bool *)calloc((size_t)ylen, sizeof(bool));
    }
    return a;
}

unsigned int **tool_alloc_array_ui(int xlen, int ylen)
{

    unsigned int **a = (unsigned int **)malloc(xlen * sizeof(unsigned int *));
    for (int x = 0; x < xlen; x++)
    {
        a[x] = (unsigned int *)calloc((size_t)ylen, sizeof(unsigned int));
    }
    return a;
}

double **tool_alloc_array_d(int xlen, int ylen)
{
    double **a = (double **)malloc(xlen * sizeof(double *));
    for (int x = 0; x < xlen; x++)
    {
        a[x] = (double *)calloc((size_t)ylen, sizeof(double));
    }
    return a;
}

void tool_array_bool_to_double(double *res, bool *src, int len)
{
    for (int i = 0; i < len; i++)
    {
        res[i] = src[i] ? 1.0 : 0.0;
    }
}

void tool_round(double **a, int xlen, int ylen)
{
    for (int x = 0; x < xlen; x++)
        for (int y = 0; y < ylen; y++)
            a[x][y] = round(a[x][y]);
}

static int tool_current_thread()
{
#ifdef _USE_OPENMP
    return omp_get_thread_num();
#else
    return 0;
#endif
}

double tool_rand()
{
    return gsl_rng_uniform(_random[tool_current_thread()]);
}

/*
 * Returns random int from [min, max-1]
 */
int tool_rand_i(int min, int max)
{
    return min + gsl_rng_uniform_int(_random[tool_current_thread()], max - min);
}

void tool_fill_random1(double *a, int xlen, bool deterministic)
{
    for (int x = 0; x < xlen; x++)
    {
        a[x] = tool_rand();
        if (deterministic)
        {
            a[x] = a[x] > 0.5 ? 1 : 0;
        }
    }
}

void tool_fill_random1ui(unsigned int *a, int xlen, int min, int max)
{
    for (int x = 0; x < xlen; x++)
    {
        a[x] = (unsigned int)tool_rand_i(min, max);
    }
}

void tool_fill_random1i(int *a, int xlen, int min, int max)
{
    for (int x = 0; x < xlen; x++)
    {
        a[x] = tool_rand_i(min, max);
    }
}

void tool_fill_random1b(bool *a, int xlen)
{
    for (int x = 0; x < xlen; x++)
    {
        a[x] = tool_rand() < 0.5;
    }
}

void tool_fill_random(double **a, int xlen, int ylen, bool deterministic)
{
    for (int x = 0; x < xlen; x++)
    {
        for (int y = 0; y < ylen; y++)
        {
            a[x][y] = tool_rand();
            if (deterministic)
            {
                a[x][y] = a[x][y] > 0.5 ? 1 : 0;
            }
        }
    }
}

void tool_fill_random_d(double **a, int xlen, int ylen)
{
    for (int x = 0; x < xlen; x++)
    {
        for (int y = 0; y < ylen; y++)
        {
            a[x][y] = tool_rand(); // < 0.5 ? 1 : 0;
        }
    }
}

void tool_fill_random_b(bool **a, int xlen, int ylen)
{
    for (int x = 0; x < xlen; x++)
    {
        double p = tool_rand();
        for (int y = 0; y < ylen; y++)
        {
            a[x][y] = tool_rand() <= p;
        }
    }
}

void tool_fill_random_ui(unsigned int **a, int xlen, int ylen, int min, int max)
{
    for (int x = 0; x < xlen; x++)
    {
        for (int y = 0; y < ylen; y++)
        {
            a[x][y] = (unsigned int)tool_rand_i(min, max);
        }
    }
}

double tool_sum(double **a, int xlen, int ylen)
{
    double res = 0;
    for (int x = 0; x < xlen; x++)
        for (int y = 0; y < ylen; y++)
            res += a[x][y];
    return res;
}

int tool_foldedSize(int s, int r)
{
    return s + 2 * r;
}

void tool_fold(bool *f_res, bool *u_src, int u_len, int r)
{
    memcpy(f_res + r, u_src, (size_t)u_len * sizeof(bool));
    memcpy(f_res, u_src + u_len - r, (size_t)r * sizeof(bool));
    memcpy(f_res + r + u_len, u_src, (size_t)r * sizeof(bool));
}

void tool_fold_d(double *f_res, double *u_src, int u_len, int r)
{
    memcpy(f_res + r, u_src, (size_t)u_len * sizeof(double));
    memcpy(f_res, u_src + u_len - r, (size_t)r * sizeof(double));
    memcpy(f_res + r + u_len, u_src, (size_t)r * sizeof(double));
}

void tool_unfold(bool u_res[], bool f_src[], int f_len, int r)
{
    memcpy(u_res, f_src + r, (size_t)(f_len - 2 * r) * sizeof(bool));
}

void tool_unfold_d(double u_res[], double f_src[], int f_len, int r)
{
    memcpy(u_res, f_src + r, (size_t)(f_len - 2 * r) * sizeof(double));
}

void tool_add(double **res, bool **src, int xlen, int ylen)
{
    for (int x = 0; x < xlen; x++)
        for (int y = 0; y < ylen; y++)
            res[x][y] += src[x][y] ? 1.0 : 0.0;
}

void tool_mult(double **a, double c, int xlen, int ylen)
{
    for (int x = 0; x < xlen; x++)
        for (int y = 0; y < ylen; y++)
            a[x][y] *= c;
}

void tool_abs_subst(double **res, bool **a, double **b, int xlen, int ylen)
{
    for (int x = 0; x < xlen; x++)
        for (int y = 0; y < ylen; y++)
        {
            res[x][y] = fabs((a[x][y] ? 1.0 : 0.0) - b[x][y]);
        }
}

double tool_1d_max(double *a, int len)
{
    double max = 0.0;
    for (int i = 0; i < len; i++)
    {
        if (a[i] > max)
            max = a[i];
    }
    return max;
}

int tool_differences(bool **a, bool **b, int xlen, int ylen)
{
    int result = 0;
    for (int x = 0; x < xlen; x++)
    {
        for (int y = 0; y < ylen; y++)
        {
            if (a[x][y] && !b[x][y])
                result++;
            if (!a[x][y] && b[x][y])
                result++;
        }
    }
    return result;
}

static int tool_num_threads()
{
#ifdef _USE_OPENMP
    return omp_get_num_threads();
#else
    return 1;
#endif
}

void tool_fake_random()
{
    int threads = 0;
#ifdef _USE_OPENMP
#pragma omp parallel
#pragma omp master
#endif
    {
        threads = tool_num_threads();
        const gsl_rng_type *T;
        gsl_rng_env_setup();
        T = gsl_rng_default;
        _random = (gsl_rng **)malloc(threads * sizeof(gsl_rng *));
        for (int i = 0; i < threads; i++)
        {
            _random[i] = gsl_rng_alloc(T);
            gsl_rng_set(_random[i], 1234567890);
        }
    }
}

void tool_init_random()
{
    int threads = 0;
#ifdef _USE_OPENMP
#pragma omp parallel
#pragma omp master
#endif
    {
        if (_random == NULL)
        {
            threads = tool_num_threads();

#ifndef _USE_RAND
#ifdef __APPLE__
            srandomdev();
#else
            srandom((unsigned int)tool_get_time());
#endif
#else
            srand((unsigned int)tool_get_time());
#endif

            const gsl_rng_type *T;

            gsl_rng_env_setup();

            T = gsl_rng_default;

            _random = (gsl_rng **)malloc(threads * sizeof(gsl_rng *));
            for (int i = 0; i < threads; i++)
            {
                _random[i] = gsl_rng_alloc(T);
                unsigned long int seed;
#ifdef _USE_RAND
                seed = (unsigned long int)rand();
#else
                seed = (unsigned long int)random();
#endif
                gsl_rng_set(_random[i], seed);
            }
        }
    }
}

double tool_rand_gauss(double sigma)
{
    return gsl_ran_gaussian_ziggurat(_random[tool_current_thread()], sigma);
}

int tool_cap(int a, int min, int max)
{
    if (a > max)
        return max;
    if (a < min)
        return min;
    return a;
}

void tool_range(int *result, int start, int len)
{
    for (int i = start; i < start + len; i++)
    {
        result[i - start] = i;
    }
}

int *tool_alloc_range(int start, int len)
{
    int *a = (int *)malloc(sizeof(int) * len);
    tool_range(a, start, len);
    return a;
}

#ifdef __APPLE__
int _compr(void *f, const void *a, const void *b)
{
#else
int _compr(const void *a, const void *b, void *f)
{
#endif
    double *fitness = (double *)f;
    int i1 = (int)(*(int *)a);
    int i2 = (int)(*(int *)b);
    double diff = fitness[i1] - fitness[i2];
    if (diff == 0.0)
        return 0;
    if (diff > 0.0)
        return 1;
    return -1;
}

int *tool_sort_fitness_index(int *index, double *fitness, int count)
{
#ifdef __APPLE__
    qsort_r(index, count, sizeof(int), fitness, _compr);
#else
    qsort_r(index, count, sizeof(int), _compr, fitness);
#endif
    return index;
}

#ifdef __APPLE__
int _compr_int(void *f, const void *a, const void *b)
{
#else
int _compr_int(const void *a, const void *b, void *f)
{
#endif
    int *fitness = (int *)f;
    int i1 = (int)(*(int *)a);
    int i2 = (int)(*(int *)b);
    int diff = fitness[i1] - fitness[i2];
    if (diff == 0)
        return 0;
    if (diff > 0)
        return 1;
    return -1;
}

int *tool_sort_index_int(int *index, int *array, int count)
{
#ifdef __APPLE__
    qsort_r(index, count, sizeof(int), array, _compr_int);
#else
    qsort_r(index, count, sizeof(int), _compr_int, array);
#endif
    return index;
}

bool tool_row_cmp(bool *r1, bool *r2, int len)
{
    for (int i = 0; i < len; i++)
    {
        if (r1[i] != r2[i])
            return false;
    }
    return true;
}

double tool_avg(double *a, int len)
{
    double result = 0;
    for (int i = 0; i < len; i++)
        result += a[i];
    return result / len;
}

double tool_max(double *a, int len)
{
    double result = a[0];
    for (int i = 1; i < len; i++)
    {
        if (result < a[i])
            result = a[i];
    }
    return result;
}

int tool_max_i(int *a, int len)
{
    int result = a[0];
    for (int i = 1; i < len; i++)
    {
        if (result < a[i])
            result = a[i];
    }
    return result;
}

double tool_min(double *a, int len)
{
    double result = a[0];
    for (int i = 1; i < len; i++)
    {
        if (result > a[i])
            result = a[i];
    }
    return result;
}

int tool_min_i(int *a, int len)
{
    int result = a[0];
    for (int i = 1; i < len; i++)
    {
        if (result > a[i])
            result = a[i];
    }
    return result;
}

double tool_max_ix(double *a, int len, int *id)
{
    double result = a[0];
    (*id) = 0;
    for (int i = 1; i < len; i++)
    {
        if (result < a[i])
        {
            result = a[i];
            (*id) = i;
        }
    }
    return result;
}

double tool_min_ix(double *a, int len, int *id)
{
    double result = a[0];
    (*id) = 0;
    for (int i = 1; i < len; i++)
    {
        if (result > a[i])
        {
            result = a[i];
            (*id) = i;
        }
    }
    return result;
}

double tool_1d_sum(double *a, int len)
{
    double result = 0;
    for (int i = 0; i < len; i++)
    {
        result += a[i];
    }
    return result;
}

bool tool_compar_b(bool *a, bool *b, int len)
{
    for (int i = 0; i < len; i++)
    {
        if (a[i] != b[i])
            return false;
    }
    return true;
}

bool tool_compar_d(double *a, double *b, int len)
{
    for (int i = 0; i < len; i++)
    {
        if (a[i] != b[i])
            return false;
    }
    return true;
}


#include <stdio.h>
#include <cblas.h>
#include <sys/time.h>
#include <stdlib.h>
#include <omp.h>

#define NUM 64

#define GEMM_K 320
#define GEMM_N 4096
#define GEMM_M 256

void PACKA(float* A, float* Ac, long M, long K, long LK)
{
	long ii, jj, kk;
	for( ii = 0 ; ii < M; ii = ii + 8)
	{

		float *temp = A + ii * LK + kk;

		asm volatile(
				"	ldr		x0, %[Ac]							\n"
				"	ldr		x1, %[K]							\n"
				"	ldr		x2, %[temp]						\n"
				"	ldr		x30, %[LK]						\n"

				"	add		x3, x2, x30, lsl #2		\n"
				"	add		x4, x2, x30, lsl #3		\n"
				"	add		x5, x3, x30, lsl #3		\n"
				"	add		x6, x4, x30, lsl #3		\n"
				"	add		x7, x5, x30, lsl #3		\n"
				"	add		x8, x6, x30, lsl #3		\n"
				"	add		x9, x7, x30, lsl #3		\n"

				"	lsr		x21, x1, #3						\n"
				"	cmp		x21, #0								\n"
				"	beq		PACKA_END							\n"

				"PACKA:												\n"

				"	prfm	PLDL1KEEP, [x2, #128]	\n"
				"	prfm	PLDL1KEEP, [x3, #128]	\n"

				"	ldr		q0, [x2], #16					\n"
				"	ldr		q1, [x3], #16					\n"
				"	ldr		q2, [x4], #16					\n"
				"	ldr		q3, [x5], #16					\n"

				"	prfm	PLDL1KEEP, [x4, #128]	\n"
				"	prfm	PLDL1KEEP, [x5, #128]	\n"

				"	st4		{v0.s, v1.s, v2.s, v3.s}[0], [x0], #16	\n"

				"	ldr		q4, [x6], #16					\n"
				"	ldr		q5, [x7], #16					\n"
				"	ldr		q6, [x8], #16					\n"
				"	ldr		q7, [x9], #16					\n"

				"	st4		{v4.s, v5.s, v6.s, v7.s}[0], [x0], #16	\n"

				"	prfm	PLDL1KEEP, [x6, #128]	\n"
				"	prfm	PLDL1KEEP, [x7, #128]	\n"

				"	ldr		q8, [x2], #16					\n"
				"	st4		{v0.s, v1.s, v2.s, v3.s}[1], [x0], #16	\n"
				"	ldr		q9, [x3], #16					\n"
				"	st4		{v4.s, v5.s, v6.s, v7.s}[1], [x0], #16	\n"
				"	ldr		q10, [x4], #16				\n"
				"	st4		{v0.s, v1.s, v2.s, v3.s}[2], [x0], #16	\n"
				"	ldr		q11, [x5], #16				\n"
				"	st4		{v4.s, v5.s, v6.s, v7.s}[2], [x0], #16	\n"

				"	prfm	PLDL1KEEP, [x8, #128]	\n"
				"	prfm	PLDL1KEEP, [x9, #128]	\n"

				"	ldr		q12, [x6], #16				\n"
				"	st4		{v0.s, v1.s, v2.s, v3.s}[3], [x0], #16		\n"
				"	ldr		q13, [x7], #16				\n"
				"	st4		{v4.s, v5.s, v6.s, v7.s}[3], [x0], #16		\n"
				"	ldr		q14, [x8], #16				\n"
				"	st4		{v8.s, v9.s, v10.s, v11.s}[0], [x0], #16	\n"
				"	ldr		q15, [x9], #16				\n"
				"	st4		{v12.s, v13.s, v14.s, v15.s}[0], [x0], #16	\n"

				"	subs	x21, x21, #1					\n"

				"	st4		{v8.s, v9.s, v10.s, v11.s}[1], [x0], #16	\n"
				"	st4		{v12.s, v13.s, v14.s, v15.s}[1], [x0], #16	\n"
				"	st4		{v8.s, v9.s, v10.s, v11.s}[2], [x0], #16	\n"
				"	st4		{v12.s, v13.s, v14.s, v15.s}[2], [x0], #16	\n"
				"	st4		{v8.s, v9.s, v10.s, v11.s}[3], [x0], #16	\n"
				"	st4		{v12.s, v13.s, v14.s, v15.s}[3], [x0], #16	\n"

				"	bgt		PACKA 						\n"

				"	ands	x22, x1, #7				\n"
				"	beq		PACKA_END					\n"

				"	cmp		x22, #4						\n"
				"	blt 	K1_PACKA					\n"

				"K4_PACKA:								\n"

				"	ldr		q0, [x2], #16			\n"
				"	ldr		q1, [x3], #16			\n"
				"	ldr		q2, [x4], #16			\n"
				"	ldr		q3, [x5], #16			\n"

				"	st4		{v0.s, v1.s, v2.s, v3.s}[0], [x0], #16	\n"
				"	ldr		q4, [x6], #16			\n"
				"	st4		{v0.s, v1.s, v2.s, v3.s}[1], [x0], #16	\n"
				"	ldr		q5, [x7], #16			\n"
				"	st4		{v0.s, v1.s, v2.s, v3.s}[2], [x0], #16	\n"
				"	ldr		q6, [x8], #16			\n"
				"	st4		{v0.s, v1.s, v2.s, v3.s}[3], [x0], #16	\n"
				"	ldr		q7, [x9], #16			\n"

				"	st4		{v4.s, v5.s, v6.s, v7.s}[0], [x0], #16	\n"
				"	st4		{v4.s, v5.s, v6.s, v7.s}[1], [x0], #16	\n"
				"	st4		{v4.s, v5.s, v6.s, v7.s}[2], [x0], #16	\n"
				"	st4		{v4.s, v5.s, v6.s, v7.s}[3], [x0], #16	\n"

				"	subs	x22, x22, #4			\n"
				"	beq		PACKA_END					\n"

				"K1_PACKA:								\n"

				"	ldr		s0, [x2], #4			\n"
				"	ldr		s1, [x3], #4			\n"
				"	ldr		s2, [x4], #4			\n"
				"	ldr		s3, [x5], #4			\n"

				"	subs 	x22, x22, #1			\n"

				"	st4		{v0.s, v1.s, v2.s, v3.s}[0], [x0], #16	\n"
				"	ldr		s4, [x6], #4			\n"
				"	ldr		s5, [x7], #4			\n"
				"	ldr		s6, [x8], #4			\n"
				"	ldr		s7, [x9], #4			\n"

				"	st4		{v4.s, v5.s, v6.s, v7.s}[0], [x0], #16	\n"

				"	bgt		K1_PACKA				\n"

				"PACKA_END:							\n"

				:
				:
				[temp] "m" (temp),
				[Ac] "m" (Ac),
				[K] "m" (K),
				[LK] "m" (LK)
				:"x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8",
				"x9", "x10", "x11", "x12", "x13","x14", "x15", "x16",
				"x17", "x18", "x19", "x20", "x21", "x22", "x23", "x24","x25",
				"x26", "x27", "x28", "x29", "x30",
				"v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7",
				"v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15",
				"v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23",
				"v24", "v25", "v26", "v27", "v28", "v29", "v30", "v31"

		);

		Ac = Ac + K * 8;

	}

}

void Sin_PACK(float* A, float* Ac, long M, long K, long LK)
{
	long ii, jj, kk;
	long Kc;

	for( kk =0 ; kk < K; kk = kk + Kc)
	{

		Kc = GEMM_K;
		if(K - kk < GEMM_K)
			Kc= K - kk;

		for( ii = 0 ; ii < M; ii = ii + 8)
		{

			float *temp = A + ii * LK + kk;

			asm volatile(
					"	ldr		x0, %[Ac]							\n"
					"	ldr		x1, %[Kc]							\n"
					"	ldr		x2, %[temp]						\n"
					"	ldr		x30, %[LK]						\n"

					"	add		x3, x2, x30, lsl #2		\n"
					"	add		x4, x2, x30, lsl #3		\n"
					"	add		x5, x3, x30, lsl #3		\n"
					"	add		x6, x4, x30, lsl #3		\n"
					"	add		x7, x5, x30, lsl #3		\n"
					"	add		x8, x6, x30, lsl #3		\n"
					"	add		x9, x7, x30, lsl #3		\n"

					"	lsr		x21, x1, #3						\n"
					"	cmp		x21, #0								\n"
					"	beq		Sin_PACKA_END					\n"

					"Sin_PACKA:										\n"

					"	prfm	PLDL1KEEP, [x2, #128]	\n"
					"	prfm	PLDL1KEEP, [x3, #128]	\n"

					"	ldr		q0, [x2], #16					\n"
					"	ldr		q1, [x3], #16					\n"
					"	ldr		q2, [x4], #16					\n"
					"	ldr		q3, [x5], #16					\n"

					"	prfm	PLDL1KEEP, [x4, #128]	\n"
					"	prfm	PLDL1KEEP, [x5, #128]	\n"

					"	st4		{v0.s, v1.s, v2.s, v3.s}[0], [x0], #16	\n"

					"	ldr		q4, [x6], #16					\n"
					"	ldr		q5, [x7], #16					\n"
					"	ldr		q6, [x8], #16					\n"
					"	ldr		q7, [x9], #16					\n"

					"	st4		{v4.s, v5.s, v6.s, v7.s}[0], [x0], #16	\n"

					"	prfm	PLDL1KEEP, [x6, #128]	\n"
					"	prfm	PLDL1KEEP, [x7, #128]	\n"

					"	ldr		q8, [x2], #16					\n"
					"	st4		{v0.s, v1.s, v2.s, v3.s}[1], [x0], #16	\n"
					"	ldr		q9, [x3], #16					\n"
					"	st4		{v4.s, v5.s, v6.s, v7.s}[1], [x0], #16	\n"
					"	ldr		q10, [x4], #16				\n"
					"	st4		{v0.s, v1.s, v2.s, v3.s}[2], [x0], #16	\n"
					"	ldr		q11, [x5], #16				\n"
					"	st4		{v4.s, v5.s, v6.s, v7.s}[2], [x0], #16	\n"

					"	prfm	PLDL1KEEP, [x8, #128]	\n"
					"	prfm	PLDL1KEEP, [x9, #128]	\n"

					"	ldr		q12, [x6], #16				\n"
					"	st4		{v0.s, v1.s, v2.s, v3.s}[3], [x0], #16		\n"
					"	ldr		q13, [x7], #16				\n"
					"	st4		{v4.s, v5.s, v6.s, v7.s}[3], [x0], #16		\n"
					"	ldr		q14, [x8], #16				\n"
					"	st4		{v8.s, v9.s, v10.s, v11.s}[0], [x0], #16	\n"
					"	ldr		q15, [x9], #16				\n"
					"	st4		{v12.s, v13.s, v14.s, v15.s}[0], [x0], #16	\n"

					"	subs	x21, x21, #1					\n"

					"	st4		{v8.s, v9.s, v10.s, v11.s}[1], [x0], #16	\n"
					"	st4		{v12.s, v13.s, v14.s, v15.s}[1], [x0], #16	\n"
					"	st4		{v8.s, v9.s, v10.s, v11.s}[2], [x0], #16	\n"
					"	st4		{v12.s, v13.s, v14.s, v15.s}[2], [x0], #16	\n"
					"	st4		{v8.s, v9.s, v10.s, v11.s}[3], [x0], #16	\n"
					"	st4		{v12.s, v13.s, v14.s, v15.s}[3], [x0], #16	\n"

					"	bgt		Sin_PACKA 				\n"

					"	ands	x22, x1, #7				\n"
					"	beq		Sin_PACKA_END			\n"

					"	cmp		x22, #4						\n"
					"	blt 	Sin_K1_PACKA			\n"

					"Sin_K4_PACKA:						\n"

					"	ldr		q0, [x2], #16			\n"
					"	ldr		q1, [x3], #16			\n"
					"	ldr		q2, [x4], #16			\n"
					"	ldr		q3, [x5], #16			\n"

					"	st4		{v0.s, v1.s, v2.s, v3.s}[0], [x0], #16	\n"
					"	ldr		q4, [x6], #16			\n"
					"	st4		{v0.s, v1.s, v2.s, v3.s}[1], [x0], #16	\n"
					"	ldr		q5, [x7], #16			\n"
					"	st4		{v0.s, v1.s, v2.s, v3.s}[2], [x0], #16	\n"
					"	ldr		q6, [x8], #16			\n"
					"	st4		{v0.s, v1.s, v2.s, v3.s}[3], [x0], #16	\n"
					"	ldr		q7, [x9], #16			\n"

					"	st4		{v4.s, v5.s, v6.s, v7.s}[0], [x0], #16	\n"
					"	st4		{v4.s, v5.s, v6.s, v7.s}[1], [x0], #16	\n"
					"	st4		{v4.s, v5.s, v6.s, v7.s}[2], [x0], #16	\n"
					"	st4		{v4.s, v5.s, v6.s, v7.s}[3], [x0], #16	\n"

					"	subs	x22, x22, #4			\n"
					"	beq		Sin_PACKA_END			\n"

					"Sin_K1_PACKA:						\n"

					"	ldr		s0, [x2], #4			\n"
					"	ldr		s1, [x3], #4			\n"
					"	ldr		s2, [x4], #4			\n"
					"	ldr		s3, [x5], #4			\n"

					"	subs 	x22, x22, #1			\n"

					"	st4		{v0.s, v1.s, v2.s, v3.s}[0], [x0], #16	\n"
					"	ldr		s4, [x6], #4			\n"
					"	ldr		s5, [x7], #4			\n"
					"	ldr		s6, [x8], #4			\n"
					"	ldr		s7, [x9], #4			\n"

					"	st4		{v4.s, v5.s, v6.s, v7.s}[0], [x0], #16	\n"

					"	bgt		Sin_K1_PACKA			\n"

					"Sin_PACKA_END:						\n"

					:
	        :
	      		[temp] "m" (temp),
	      		[Ac] "m" (Ac),
	      		[Kc] "m" (Kc),
	      		[LK] "m" (LK)
	        :"x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8",
	        "x9", "x10", "x11", "x12", "x13","x14", "x15", "x16",
	        "x17", "x18", "x19", "x20", "x21", "x22", "x23", "x24","x25",
	        "x26", "x27", "x28", "x29", "x30",
	        "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7",
	        "v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15",
	        "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23",
	        "v24", "v25", "v26", "v27", "v28", "v29", "v30", "v31"

			);

			Ac = Ac + Kc * 8;

		}
	}
}


// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#pragma once

#include "Data\Types.h"
#include "MageSettings.h"
#include "Image\ORBDescriptor.h"
#include "BaseFeatureMatcher.h"

#include <vector>
#include <gsl\gsl>
#include <memory>

namespace mage
{
    class Keyframe;
    class AnalyzedImage;

    class BaseBow
    {
    public:
        struct QueryMatch
        {
            Id<Keyframe> Id;
            float Score;

            operator mage::Id<Keyframe>() const
            {
                return Id;
            }
        };

        BaseBow(const BagOfWordsSettings& settings)
            :m_settings{ settings }
        {}

        virtual ~BaseBow() {}

        virtual void AddTrainingDescriptors(gsl::span<const ORBDescriptor>) {}

        virtual void AddImage(const Id<Keyframe>, const AnalyzedImage& image) = 0;

        virtual void RemoveImage(const Id<Keyframe>) = 0;

        virtual size_t QueryFeatures(const ORBDescriptor& descriptor, const Id<Keyframe>& keyframe, std::vector<ptrdiff_t>& features) const = 0;

        virtual std::unique_ptr<BaseFeatureMatcher> CreateFeatureMatcher(const Id<Keyframe>& id, gsl::span<const ORBDescriptor> features) const = 0;

        virtual std::unique_ptr<BaseBow> CreateTemporaryBow() const = 0;

        virtual std::vector<QueryMatch> QueryUnknownImage(gsl::span<const ORBDescriptor> descriptors, size_t maxResults) const = 0;

        virtual void Clear() = 0;

        virtual bool IsTrainingDone() const { return true;}

    protected:
        const BagOfWordsSettings& m_settings;
    };
}


#ifndef __GSL_PERMUTE_MATRIX_H__
#define __GSL_PERMUTE_MATRIX_H__

#include <gsl/gsl_permute_matrix_complex_long_double.h>
#include <gsl/gsl_permute_matrix_complex_double.h>
#include <gsl/gsl_permute_matrix_complex_float.h>

#include <gsl/gsl_permute_matrix_long_double.h>
#include <gsl/gsl_permute_matrix_double.h>
#include <gsl/gsl_permute_matrix_float.h>

#include <gsl/gsl_permute_matrix_ulong.h>
#include <gsl/gsl_permute_matrix_long.h>

#include <gsl/gsl_permute_matrix_uint.h>
#include <gsl/gsl_permute_matrix_int.h>

#include <gsl/gsl_permute_matrix_ushort.h>
#include <gsl/gsl_permute_matrix_short.h>

#include <gsl/gsl_permute_matrix_uchar.h>
#include <gsl/gsl_permute_matrix_char.h>

#endif /* __GSL_PERMUTE_MATRIX_H__ */


#ifndef __GSL_SORT_VECTOR_H__
#define __GSL_SORT_VECTOR_H__

#include <gsl/gsl_sort_vector_long_double.h>
#include <gsl/gsl_sort_vector_double.h>
#include <gsl/gsl_sort_vector_float.h>

#include <gsl/gsl_sort_vector_ulong.h>
#include <gsl/gsl_sort_vector_long.h>

#include <gsl/gsl_sort_vector_uint.h>
#include <gsl/gsl_sort_vector_int.h>

#include <gsl/gsl_sort_vector_ushort.h>
#include <gsl/gsl_sort_vector_short.h>

#include <gsl/gsl_sort_vector_uchar.h>
#include <gsl/gsl_sort_vector_char.h>

#endif /* __GSL_SORT_VECTOR_H__ */


#pragma once
#include <gsl/span>
#include "halley/core/api/audio_api.h"
#include "audio_buffer.h"

#if defined(_M_X64) || defined(__x86_64__)
#define HAS_SSE
#if !defined(__linux__)
#define HAS_AVX
#endif
#endif

#if defined(_M_IX86) || defined(__i386)
// Might not be available, but do we really care about such old processors?
#define HAS_SSE
#endif

namespace Halley
{
	class AudioMixer
	{
	public:
		virtual ~AudioMixer() {}

		virtual void mixAudio(gsl::span<const AudioSamplePack> src, gsl::span<AudioSamplePack> dst, float gainStart, float gainEnd);
		virtual void interleaveChannels(gsl::span<AudioSamplePack> dst, gsl::span<AudioBuffer*> src);
		virtual void compressRange(gsl::span<AudioSamplePack> buffer);
		static std::unique_ptr<AudioMixer> makeMixer();
	};
}


#pragma once
#include "Hypo/Graphics/Exports.h"
#include "Hypo/System/DataTypes/ObjPtr.h"
#include "Hypo/Window/Context/GraphicsContext.h"
#include <gsl/span>

namespace Hypo
{

	enum class TextureType
	{
		None,
		Rgb,
		Rgba,
		sRgb,
		sRgba,
		Stencil,
		Depth
	};

	HYPO_GRAPHICS_API uInt32 GetTexturePixelSize(TextureType type);
	HYPO_GRAPHICS_API uInt32 GetTextureSize(uInt32 width, uInt32 height, TextureType type);

	class TextureData
	{
	public:
		TextureData(uInt32 width, uInt32 height, TextureType type);

		TextureData(uInt32 width, uInt32 height, TextureType type, std::vector<Byte> pixels);

		TextureData() = default;
		TextureData(const TextureData&) = default;

		uInt32 GetWidth() const { return m_Width; }
		uInt32 GetHeight() const { return m_Height; }
		TextureType GetType()const { return m_Type; }
		uInt32 GetPixelsSize() const { return m_Pixels.size(); }
		const std::vector<Byte>& GetPixels() const { return m_Pixels; }
	private:
		uInt32 m_Width = 0;
		uInt32 m_Height = 0;
		TextureType m_Type = TextureType::None;
		std::vector<Byte> m_Pixels;
	};

	HYPO_GRAPHICS_API TextureData TextureFromFile(std::string path);
}