////////////////////////////////////////////////////////////////////////
//		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, *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;
    
    // Some minor input/output error checking
    if (!(nrhs==15)) {
        mexErrMsgTxt("Need 14 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] );
    // 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 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]);
    
    //Initialize Fu
    for(i=0 ; i < Nx * Ny ; i++) {
        Fu[i] = 0.0;
    }
    
    
    for(i=1;i<Nx+1;i++) {
        for(j=1;j<Ny+1;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 
            {  //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;
                } 
		//Set flux of points next to obstacles to zero.
                    if(NodeP[j+(i-1)*(Ny+2)]<=Nbcs-4) //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) Right\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) {
                        //East 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;
                }  
               
		//Set flux of points next to obstacles to zero.
                    if(NodeP[j+(i+1)*(Ny+2)]<=Nbcs-4) //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);
                if (i==Nx){
                }
                
/////////////////// 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;
                }
                
		//Set flux of points next to obstacles to zero.
                    if(NodeP[j+1+ i*(Ny+2)]<=Nbcs-4) //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;
                }
		//Set flux of points next to obstacles to zero.
                    if(NodeP[j-1+ i*(Ny+2)]<=Nbcs-4) //if top 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;         
            }
            
        }
    }
}