////////////////////////////////////////////////////////////////////////
//		THIS FILE IS THE INTERFACE FILE BETWEEN MATLAB AND C  //
////////////////////////////////////////////////////////////////////////

// Authors:   Matt Ueckermann, Pierre Lermusiaux, Pat Haley for MIT course 2.29

////////////////////////////////////
//  DECLARE STANDARD HEADER FILES //
////////////////////////////////////

#include "math.h"
#include "mex.h"
#include <stdlib.h>
#include <string.h>

#define MIN(a, b) ((a)>(b)?(b):(a))
#define MAX(a, b) ((a)<(b)?(b):(a))

int sign(float v) {
    return((v<0)-(v>0));
}
/////////////////////////////
// DEFINE LIMITER FUNCTION //
/////////////////////////////
double TVD(double phi) {
    //MC limiter
    return(MAX(0.0, MIN(MIN(0.5 * (1.0 + phi), 2.0), 2.0 * phi)));
    //minmod limiter
//      return(((phi) > (0) ? MIN(phi,1) : 0.0));
    //superbee
//      return(MAX(MAX(0,MIN(1.0, 2.0 * phi)),MIN(2,phi)));
    //Upwind
//   return(0.0);
    // Lax Wendroff (NOT TVD)
//    return(1.0);
}

////////////////////////////
//  MASTER FUNCTION FILE  //
////////////////////////////


//////////////////////////////////////////
// MATLAB INTERFACE FUNCTION DEFINITION //
//////////////////////////////////////////

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {
    // Declare input variables
    double dx, dy, dt, NBlen, *u, *v, *rho;
    int Nx, Ny, *Nodeu, *Nodev, *NodeP, Nbcs, NbcsDP, NbcsDu, NbcsDv;
    
    
    // Declare output variables
    double *Fu;
    
    //Declare working variables
    int i, j;
    double fl, fr, fu, fd, theta, U, sumRHO, diffRHO;
    double lenx, leny, len, lenxxy, lenxyy;
    
    // Some minor input/output error checking
    if (!(nrhs==16)) {
        mexErrMsgTxt("Need 16 input arguments");
    }
    if (!(nlhs==1)) {
        mexErrMsgTxt("Need 1 outputs arguments");
    }
    
    
    // Get access to input data from MATLAB
    // Pointers are used for large array
    // Scalar are re-created in memory
    
    /********!!!! WARNING !!!************/
    // WARNING !!!!  note that C is expecting to be pointing to type int32 from
    // Matlab.  Therefore, for this code to work, the C argument must be
    // int32.  This is done in matlab by int32(C);
    // The function is then called like [Bnew Cnew] = axB(5,B,int32(C));
    // Use double(Cnew) if you want to convert it back (this might slow things).
    /********!!!! WARNING !!!************/
    //[Fu Fv] = UVadvect_DO_CDS(Nx, Ny, dx, dy, dt, ui, vi, uj, vj,
    //                          Nodeu, Nodev, idsu, idsv)
    Nx      =   (int)     mxGetScalar( prhs[0]  );
    Ny      =   (int)     mxGetScalar( prhs[1]  );
    dx      =   (double)  mxGetScalar( prhs[2]  );
    dy      =   (double)  mxGetScalar( prhs[3]  );
    dt      =   (double)  mxGetScalar( prhs[4]  );
    rho     =   (double*) mxGetPr(     prhs[5]  );
    u       =   (double*) mxGetPr(     prhs[6]  );
    v       =   (double*) mxGetPr(     prhs[7]  );
    NodeP   =   (int*)    mxGetPr(     prhs[8]  );
    Nodeu   =   (int*)    mxGetPr(     prhs[9]  );
    Nodev   =   (int*)    mxGetPr(     prhs[10] );
    Nbcs    =   (int)     mxGetScalar( prhs[11] );
    NbcsDP  =   (int)     mxGetScalar( prhs[12] );
    NbcsDu  =   (int)     mxGetScalar( prhs[13] );
    NbcsDv  =   (int)     mxGetScalar( prhs[14] );
    NBlen   =   (double)  mxGetScalar( prhs[15] );
    // Example of outputting text to MATLAB
    //     if (time ==0)
    //         mexPrintf("Welcome message for time %f\n",time);
    
    // These macros allow easy access to values of rho and u at
    //             UU
    //              U
    //      LL   L  X  R  RR
    //              D
    //             DD
    //  with respect to the current cell
    #define RHOM rho[NodeP[j  + i * (Ny + 2)] - 1]
    #define RHOUU rho[NodeP[j - 2  + i * (Ny + 2)] - 1]
    #define RHOU rho[NodeP[j - 1  + i   *(Ny + 2)] - 1]
    #define RHOD rho[NodeP[j + 1 + i * (Ny + 2)] - 1]
    #define RHODD rho[NodeP[j + 2 + i * (Ny + 2)] - 1]
    #define RHOLL rho[NodeP[j + (i - 2) * (Ny + 2)] - 1]
    #define RHOL rho[NodeP[j + (i - 1) * (Ny + 2)] - 1]
    #define RHOR rho[NodeP[j + (i + 1) * (Ny + 2)] - 1]
    #define RHORR rho[NodeP[j + (i + 2) * (Ny + 2)] - 1]

    #define RHORD rho[NodeP[j + 1 + (i + 1) * (Ny + 2)] - 1]
    #define RHORU rho[NodeP[j - 1 + (i + 1) * (Ny + 2)] - 1]
    #define RHOLD rho[NodeP[j + 1 + (i - 1) * (Ny + 2)] - 1]        
    #define RHOLU rho[NodeP[j - 1 + (i - 1) * (Ny + 2)] - 1]  
        
    #define RHORRD rho[NodeP[j + 1 + (i + 2) * (Ny + 2)] - 1]
    #define RHORRU rho[NodeP[j - 1 + (i + 2) * (Ny + 2)] - 1]
    #define RHOLLD rho[NodeP[j + 1 + (i - 2) * (Ny + 2)] - 1]
    #define RHOLLU rho[NodeP[j - 1 + (i - 2) * (Ny + 2)] - 1] 
            
    #define RHORDD rho[NodeP[j + 2 + (i + 1) * (Ny + 2)] - 1]
    #define RHORUU rho[NodeP[j - 2 + (i + 1) * (Ny + 2)] - 1]
    #define RHOLDD rho[NodeP[j + 2 + (i - 1) * (Ny + 2)] - 1]
    #define RHOLUU rho[NodeP[j - 2 + (i - 1) * (Ny + 2)] - 1] 
            
    #define RHORRDD rho[NodeP[j + 2 + (i + 2) * (Ny + 2)] - 1]
    #define RHORRUU rho[NodeP[j - 2 + (i + 2) * (Ny + 2)] - 1]
    #define RHOLLDD rho[NodeP[j + 2 + (i - 2) * (Ny + 2)] - 1]
    #define RHOLLUU rho[NodeP[j - 2 + (i - 2) * (Ny + 2)] - 1] 
            
    #define UU v[Nodev[j - 1 + (i) * (Ny + 1)] - 1]
    #define UD v[Nodev[j + (i) * (Ny + 1)] - 1]
    #define UL u[Nodeu[j + (i - 1) * (Ny + 2)] - 1]
    #define UR u[Nodeu[j + (i)* (Ny + 2)] - 1]
            
            // ALLOCATE AND POINT TO OUTPUTS
            plhs[0] =   mxCreateDoubleMatrix((Nx)* Ny   , 1, mxREAL);
    Fu      =   (double*)mxGetPr(plhs[0]);
    
   // Calculate lengths to determine narrow band region
    lenx = dx*NBlen;
    leny = dy*NBlen;
    len  = sqrt(lenx * lenx + leny * leny);
    lenxxy = sqrt(4 * lenx * lenx + leny * leny);
    lenxyy = sqrt(lenx * lenx + 4 * leny * leny);
    
    //for(i=1;i<Nx+1;i++) {
    //    for(j=1;j<Ny+1;j++) {
    //TO avoid segfaults, only go within 2 CV's of the boundary
    for(i=2; i<Nx; i++) {
        for(j=2; j<Ny; j++) {
            //If this is a boundary node, don't do any work
            if (NodeP[j  +(i)*(Ny+2)]<=Nbcs) {
                Fu[j-1+(i-1)*Ny] = 0.0;
            }
            else {
                //If this is in the narrowband do work
                double lenx;
                double leny;
                lenx = dx * NBlen;
                leny = dy * NBlen;
                 if ((fabs(RHOM) < MAX(lenx, leny)) || \
                        (fabs(RHOU) < leny) || (fabs(RHOUU) < 2 * leny) || \
                        (fabs(RHOD) < leny) || (fabs(RHODD) < 2 * leny) || \
                        (fabs(RHOL) < lenx) || (fabs(RHOLL) < 2 * lenx) || \
                        (fabs(RHOR) < lenx) || (fabs(RHORR) < 2 * lenx) || \
                        (fabs(RHORD) < len) || (fabs(RHORU) < len)  || \
                        (fabs(RHOLD) < len) || (fabs(RHOLU) < len)  || \
                        (fabs(RHORRD) < lenxxy)||(fabs(RHORRU) < lenxxy)|| \
                        (fabs(RHOLLD) < lenxxy)||(fabs(RHOLLU) < lenxxy)|| \
                        (fabs(RHORDD) < lenxyy)||(fabs(RHORUU) < lenxyy)|| \
                        (fabs(RHOLDD) < lenxyy)||(fabs(RHOLUU) < lenxyy)|| \
                        (fabs(RHORRDD) < 2 * len)||(fabs(RHORRUU) < 2 * len)|| \
                        (fabs(RHOLLDD) < 2 * len)||(fabs(RHOLLUU) < 2 * len)) {            
                    //do work
                    //U advection, TVD scheme
                    
/////////////////// WEST or LEFT FACE //////////////////
                    
                    //mexPrintf("(%d,%d) west\n",i,j);
                    if(NodeP[j  +(i-1)*(Ny+2)]>Nbcs) {
                        //West face
                        U = UL;
                        sumRHO = RHOM + RHOL;
                        diffRHO = RHOM - RHOL;
                        theta = ((U + fabs(U)) / 2.0 * (RHOL - RHOLL) \
                                + (U - fabs(U)) / 2.0 * (RHOR - RHOM)) \
                                / (diffRHO) / U;}
	
                    else //treat boundary condition
                    {
                        if (Nodeu[j  +(i-1)*(Ny+2)]<=NbcsDu || Nodeu[j  +(i-1)*(Ny+2)]>Nbcs) {
                            //West face
                            U = UL;
                        }
                        else {
                            U = UL + UR;
                        }
                        if (NodeP[j  +(i-1)*(Ny+2)]<=NbcsDP) {
                            //West face
                            sumRHO = RHOM + RHOL;
                            diffRHO = RHOM - RHOL;
                            theta = ((U + fabs(U)) / 2.0 * (RHOM - RHOL) \
                                    + (U - fabs(U)) / 2.0 * (RHOR - RHOM)) \
                                    / (diffRHO) / U;
                        }
                        else { //Neumann BC
                            sumRHO = 2.0 * RHOM + 0.5 * RHOL;
                            diffRHO = RHOM - (RHOM + 0.5 * RHOL);
                            theta = ((U + fabs(U)) / 2.0 * (RHOM - (RHOM + 0.5 * RHOL)) \
                                    + (U - fabs(U)) / 2.0 * (RHOR - RHOM)) \
                                    / (diffRHO) / U;
                        }
                    }
                    //Treat additional boundary problems. The theta in TVD
                    // won't be correct if neumann boundaries are at R
                    if (i-2>=0) { //This is to avoid segfaults
                        if(NodeP[j+(i-2)*(Ny+2)]<=Nbcs && NodeP[j+(i-2)*(Ny+2)]>NbcsDP) {
                            //if the LL boundary is Neumann, use
                            theta = ((U + fabs(U)) / 2.0 * (RHOL - (RHOL + 0.5 * RHOLL)) \
                                    + (U - fabs(U)) / 2.0 * (RHOR - RHOM)) \
                                    / (diffRHO) / U;
                        }
                    }
                    if(NodeP[j+(i+1)*(Ny+2)]<=Nbcs && NodeP[j+(i+1)*(Ny+2)]>NbcsDP) {
                        //if the most RR boundary is Neumann use:
                        theta = ((U + fabs(U)) / 2.0 * (RHOL - RHOLL) \
                                + (U - fabs(U)) / 2.0 * ((RHOM + 0.5 * RHOR) - RHOM)) \
                                / (diffRHO) / U;
                    }
                   //TapModification 
                    if(NodeP[j+(i-1)*(Ny+2)]<=Nbcs) //if left cell falls inside boundaries
		    {theta=0;sumRHO=0;diffRHO=0;}		    	
                    

		   fl = 0.5 * U * (sumRHO) \
                            + 0.5 * fabs(U) * (-1.0 + (1.0 - fabs(U * dt / dx)) * TVD(theta)) \
                            * (diffRHO);
                    
                    
/////////////////// EAST or RIGHT FACE //////////////////
                    //mexPrintf("(%d,%d) east\n",i,j);
                    if(NodeP[j  +(i+1)*(Ny+2)]>Nbcs) {
                        //East face
                        U = UR;
                        sumRHO = RHOR + RHOM;
                        diffRHO = RHOR - RHOM;
                        theta = ((U + fabs(U)) / 2.0 * (RHOM - RHOL) \
                                + (U - fabs(U)) / 2.0 * (RHORR - RHOR)) \
                                / (diffRHO) / U;
                    }
                    else //treat boundary condition
                    {
                        if (Nodeu[j  +(i)*(Ny+2)]<=NbcsDu || Nodeu[j  +(i)*(Ny+2)]>Nbcs) {
                            //West face
                            U = UR;
                        }
                        
                        else {
                            U = UL + UR;
                        }
                        
                        if (NodeP[j  +(i+1)*(Ny+2)]<=NbcsDP) {
                            //East face
                            sumRHO = RHOR + RHOM;
                            diffRHO = RHOR - RHOM;
                            theta = ((U + fabs(U)) / 2.0 * (RHOM - RHOL) \
                                    + (U - fabs(U)) / 2.0 * (RHOR - RHOM)) \
                                    / (diffRHO) / U;
                        }
                        else { //Neumann BC
                            //   mexPrintf("(%d,%d) in sNBC\n",i,j);
                            sumRHO = 2.0 * RHOM + 0.5 * RHOR;
                            diffRHO = (RHOM + 0.5 * RHOR) - RHOM;
                            theta = ((U + fabs(U)) / 2.0 * (RHOM - RHOL) \
                                    + (U - fabs(U)) / 2.0 * ((RHOM + 0.5 * RHOR) - RHOM)) \
                                    / (diffRHO) / U;
                        }
                    }
                    //Treat additional boundary problems. The theta in TVD
                    // won't be correct if neumann boundaries are at
                    if (i + 2 <= Nx + 1) { //This is to avoid segfaults
                        if(NodeP[j+(i+2)*(Ny+2)]<=Nbcs && NodeP[j+(i+2)*(Ny+2)]>NbcsDP) {
                            //if the RR boundary is Neumann, use
                            theta = ((U + fabs(U)) / 2.0 * (RHOM - RHOL) \
                                    + (U - fabs(U)) / 2.0 * ((RHOR + 0.5*RHORR) - RHOR)) \
                                    / (diffRHO) / U;
                        }
                    }
                    if(NodeP[j+(i-1)*(Ny+2)]<=Nbcs && NodeP[j+(i-1)*(Ny+2)]>NbcsDP) {
                        //if the most L boundary is Neumann use:
                        theta = ((U + fabs(U)) / 2.0 * (RHOM - (RHOM + 0.5 * RHOL)) \
                                + (U - fabs(U)) / 2.0 * (RHORR - RHOR)) \
                                / (diffRHO) / U;
                    }
                   //TapModification 
                    if(NodeP[j+(i+1)*(Ny+2)]<=Nbcs) //if right cell falls inside boundaries
		    {theta=0;sumRHO=0;diffRHO=0;}		    	
                    
                    fr =  0.5 * U * (sumRHO) \
                            + 0.5 * fabs(U) * (-1.0 + (1.0 - fabs(U * dt / dx)) * TVD(theta)) \
                            * (diffRHO);
                    
                    
/////////////////// SOUTH or DOWN FACE //////////////////
                    
                    if (NodeP[j+1+ i*(Ny+2)]>Nbcs) {
                        //South Face
                        U = UD;
                        sumRHO = RHOD + RHOM;
                        diffRHO = RHOM - RHOD;
                        theta = ((U + fabs(U)) / 2.0 * (RHOD - RHODD) \
                                + (U - fabs(U)) / 2.0 * (RHOU - RHOM)) \
                                / (diffRHO) / U;
                    }
                    else { //treat boundaries
                        if (Nodev[j +(i)*(Ny+1)]<=NbcsDv || Nodev[j +(i)*(Ny+1)]>Nbcs) { //Dirichlet on v
                            U = UD;
                        }
                        else {
                            U = UD + UU;
                        }
                        if (NodeP[j+1+(i)*(Ny+2)]<=NbcsDP) { //Dirichlet on v
                            sumRHO = RHOD + RHOM;
                            diffRHO = RHOM - RHOD;
                            theta = ((U + fabs(U)) / 2.0* (RHOM - RHOD) \
                                    + (U - fabs(U)) / 2.0 * (RHOU - RHOM)) \
                                    / (diffRHO) / U;
                        }
                        else { //Neumann on u
                            sumRHO = RHOD + 2.0 * RHOM;
                            diffRHO = RHOM - (RHOM + 0.5 * RHOD);
                            theta = ((U + fabs(U)) / 2.0 * (RHOM - (RHOM + 0.5 * RHOD)) \
                                    + (U - fabs(U)) / 2.0 * (RHOU - RHOM)) \
                                    / (diffRHO) / U;
                        }
                    }
                    //Treat additional boundary problems. The QUICk scheme won't
                    // work if neumann boundaries are at i-2 or i+1
                    if (j+2<=Ny+1) { //This is to avoid segfaults
                        if(NodeP[j+2+i*(Ny+2)]<=Nbcs && NodeP[j+2+i*(Ny+2)]>NbcsDP) {
                            //if the most south boundary is Neumann use:
                            theta = ((U + fabs(U)) / 2.0 * (RHOD - (RHOD + 0.5 * RHODD)) \
                                    + (U - fabs(U)) / 2.0 * (RHOU - RHOM)) \
                                    / (diffRHO) / U;
                        }
                    }
                    if(NodeP[j-1+i*(Ny+2)]<=Nbcs && NodeP[j-1+i*(Ny+2)]>NbcsDP) {
                        //if the most north boundary is Neumann use:
                        theta = ((U + fabs(U)) / 2.0 * (RHOD - RHODD) \
                                + (U - fabs(U)) / 2.0 * ((RHOM + 0.5 * RHOM) - RHOM)) \
                                / (diffRHO) / U;
                    }
                    
                    //TapModification 
                    if(NodeP[j+1+ i*(Ny+2)]<=Nbcs) //if south cell falls inside boundaries
		    {theta=0;sumRHO=0;diffRHO=0;}		    	
                     
                    fd = 0.5 * U*(sumRHO) \
                            + 0.5 * fabs(U) * (-1.0 + (1.0 - fabs(U * dt / dy)) * TVD(theta)) \
                            * (diffRHO);
                    
/////////////////// NORTH or UP FACE //////////////////
                    //North Face
                    if (NodeP[j-1+ i*(Ny+2)]>Nbcs) {
                        U = UU;
                        sumRHO = RHOU + RHOM;
                        diffRHO = RHOU - RHOM;
                        theta = ((U + fabs(U)) / 2.0 * (RHOM - RHOD) \
                                + (U - fabs(U)) / 2.0 * (RHOUU - RHOU)) \
                                / (diffRHO) / U;
                    }
                    else { //treat boundaries
                        if (Nodev[j-1 +(i)*(Ny+1)]<=NbcsDv || Nodev[j-1 +(i)*(Ny+1)]>Nbcs) { //Dirichlet on v
                            U = UU;
                        }
                        else { //Neuman on v
                            U = UU + UD;
                        }
                        if (NodeP[j-1+(i)*(Ny+2)]<=NbcsDP) { //Dirichlet on u
                            sumRHO = RHOU + RHOM;
                            diffRHO = RHOU - RHOM;
                            theta = ((U + fabs(U)) / 2.0 * (RHOM - RHOD) \
                                    + (U - fabs(U)) / 2.0 * (RHOU - RHOM)) \
                                    / (diffRHO) / U;
                        }
                        else { //Neuman on u
                            sumRHO = 0.5 * RHOU + 2.0 * RHOM;
                            diffRHO = (0.5 * RHOU + RHOM) - RHOM;
                            theta = ((U + fabs(U)) / 2.0 * (RHOM - RHOD) \
                                    + (U - fabs(U)) / 2.0 * ((0.5 * RHOU + RHOM) - RHOM)) \
                                    / (diffRHO) / U;
                        }
                    }
                    //Treat additional boundary problems. The QUICk scheme won't
                    // work if neumann boundaries are at i-2 or i+1
                    if (j-2>=0) { //This is to avoid segfaults
                        if(NodeP[j-2+i*(Ny+2)]<=Nbcs && NodeP[j-2+i*(Ny+2)]>NbcsDP) {
                            //if the most north boundary is Neumann use
                            theta = ((U + fabs(U)) / 2.0 * (RHOM - RHOD) \
                                    + (U - fabs(U)) / 2.0 * ((RHOU + 0.5 * RHOUU) - RHOU)) \
                                    / (diffRHO) / U;
                        }
                    }
                    if(NodeP[j+1+i*(Ny+2)]<=Nbcs && NodeP[j+1+i*(Ny+2)]>NbcsDP) {
                        //if the most south boundary is Neumann use
                        theta = ((U + fabs(U)) / 2.0 * (RHOM - (RHOM + 0.5 * RHOD)) \
                                + (U - fabs(U)) / 2.0 * (RHOUU - RHOU)) \
                                / (diffRHO) / U;
                    }
                    //TapModification 
                    if(NodeP[j-1+ i*(Ny+2)]<=Nbcs) //if left cell falls inside boundaries
		    {theta=0;sumRHO=0;diffRHO=0;}		    	
                    
                    fu = 0.5 * U*(sumRHO) \
                            + 0.5 * fabs(U) * (-1.0 + (1.0 - fabs(U * dt / dy)) * TVD(theta)) \
                            * (diffRHO);
                    
                    //Calculate Total flux change for this cell
                    Fu[j-1+(i-1)*Ny] = (fl - fr) / dx + (fd - fu) / dy;
		}           
                else {
                    Fu[j-1+(i-1)*Ny] = 0.0;
                }
            }    
        }
    }
}