////////////////////////////////////////////////////////////////////////
//		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, NBlen;
    int Nx, Ny, *Nodeu, *Nodev, *NodeP, Nbcs, NbcsDP, NbcsDu, NbcsDv;
    int *NBids;
    
    // 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==2)) {
        mexErrMsgTxt("Need 2 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]);
    plhs[1] =   mxCreateNumericMatrix(Nx* Ny   , 1, mxINT32_CLASS, mxREAL);
    NBids   =   (int*)mxGetPr(plhs[1]);
    
    // 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;
                double tmpright;
		        double tmpleft;
                double tmptop;
		        double tmpbottom;
                lenx = dx * NBlen;
                leny = dy * NBlen;
                len  = sqrt(lenx * lenx + leny * leny);
                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)) {

                    //Set the narrow band flag to indicate that this cell is 
                    //active
                    NBids[j-1  + (i-1) * (Ny)] = 1;
                    
                   //   if (RHOR<=RHOM){tmpleft = 1;}
			       //   else {tmpleft = 0;}
                    
                   //   if (RHOR>=RHOM){tmpright = 1;}
 		   //	else {tmpright=0;}

                   //    fl = (((1-tmpleft)*fabs(RHOR - RHOM)/dx)+((1-tmpright)*fabs(RHOM-RHOL)/dx))/(MAX(tmpleft+tmpright,1));  
                   //    fl = (((1-tmpleft)*fabs(RHOM - RHOL)/dx)+((1-tmpright)*fabs(RHOR-RHOM)/dx))/(MAX(tmpleft+tmpright,1));  
                   // fl = (RHOM - MIN(RHOL,RHOR)) / dx; (Older version) 
                   //    fl = (RHOM - MIN(MIN(RHOL,RHOM),MIN(RHOM,RHOR))) / dx; (Works partially)
                    if (RHOL<=RHOM && RHOM>=RHOR)
                       {fl = (fabs(RHOM-RHOR) + fabs(RHOM-RHOL))/(2*dx); }   
                    if (RHOL>=RHOM && RHOM>=RHOR)
                       {fl = (fabs(RHOM-RHOR))/(dx); }
		            if (RHOL<=RHOM && RHOM<=RHOR)
                       {fl = (fabs(RHOM-RHOL))/(dx); }
                    if (RHOL>=RHOM && RHOM<=RHOR)
                       {fl = 0; }
		   // Prevent Obstacles from affecting flux at cells adjacent to them//
		    
                   if (NodeP[j  +(i-1)*(Ny+2)]<=Nbcs && NodeP[j  +(i+1)*(Ny+2)]<=Nbcs) // If cell to the left and cell to right are within obstacle
			{fl = 0;}    //Then no flux in x direction in CV
  		   if (NodeP[j  +(i-1)*(Ny+2)]<=Nbcs && NodeP[j  +(i+1)*(Ny+2)]>Nbcs) // If only cell to the left is within obstacle
			{if(RHOR<=RHOM)                          
                         {fl = (fabs(RHOM-RHOR))/(dx);}
			 else 
                         {fl = 0;} //Take flux only if upwind condition satisfied, otherwise flux is zero
                        } //Then take flux contribution due to the right cell
                   if (NodeP[j  +(i-1)*(Ny+2)]>Nbcs && NodeP[j  +(i+1)*(Ny+2)]<=Nbcs)//If only cell to the right is within obstacle
			{if(RHOL<=RHOM) //Is information coming towards the obstacle? If yes, it is upwind direction and take flux
                         {fl = (fabs(RHOM-RHOL))/(dx);}
			 else
 			 {fl=0;}        //Otherwise no flux in this cell 
			} //Then take flux contribution due to the left cell
                   // If both sides don't have obstacles, then take flux originally computed.
                   
                   // if (RHOD<=RHOM){tmptop = 1;}
			//else {tmptop = 0;}
                    
                   // if (RHOD>=RHOM){tmpbottom = 1;}
 			//else {tmpbottom=0;}

                    //fd = (((1-tmptop)*fabs(RHOD - RHOM)/dy)+((1-tmpbottom)*fabs(RHOM-RHOU)/dy))/(MAX(tmptop+tmpbottom,1));  
                    //fd = (((1-tmptop)*fabs(RHOM - RHOU)/dy)+((1-tmpbottom)*fabs(RHOD-RHOM)/dy))/(MAX(tmptop+tmpbottom,1));   


		    //fd = (RHOM - MIN(MIN(RHOU,RHOM),MIN(RHOM,RHOD))) / dy; (Works partially)
                    //fd = (RHOM - MIN(RHOU,RHOD)) / dy;    (Older version)
                    if (RHOU<=RHOM && RHOM>=RHOD)
                       {fd = (fabs(RHOM-RHOU) + fabs(RHOM-RHOD))/(2*dy); }   
                    if (RHOU>=RHOM && RHOM>=RHOD)
                       {fd = (fabs(RHOM-RHOD))/(dy); }
		    if (RHOU<=RHOM && RHOM<=RHOD)
                       {fd = (fabs(RHOM-RHOU))/(dy); }
                    if (RHOU>=RHOM && RHOM<=RHOD)
                       {fd = 0; }

		   // Prevent Obstacles from affecting flux at cells adjacent to them//
		    
                   if (NodeP[j+1  +(i)*(Ny+2)]<=Nbcs && NodeP[j-1  +(i)*(Ny+2)]<=Nbcs) // If cell to the top and cell to bottom are within obstacle
			{fd = 0;}    //Then no flux in y direction in CV
  		   if (NodeP[j+1  +(i)*(Ny+2)]<=Nbcs && NodeP[j-1  +(i)*(Ny+2)]>Nbcs) // If only cell to the bottom is within obstacle
			{if(RHOU<=RHOM)
			  {fd = (fabs(RHOM-RHOU))/(dy);}//Is it upwind? Then take flux contribution due to the upper cell
			 else
			  {fd=0;} //Else, no flux
                        } 
                   if (NodeP[j+1  +(i)*(Ny+2)]>Nbcs && NodeP[j-1  +(i)*(Ny+2)]<=Nbcs) //If only cell to the top is within obstacle
			{if(RHOD<=RHOM)
			 {fd = (fabs(RHOM-RHOD))/(dy);}
			 else
			 {fd=0;}
			} //Then take flux contribution due to the lower cell
                   // If both sides don't have obstacles, then take flux originally computed.



                    //Calculate Total flux change for this cell
                    Fu[j-1+(i-1)*Ny] = sqrt(fl * fl + fd * fd);
                }
                else { //If this is not inside the narrowband, set the flux zero
                       //and set the narrow band flag to indicate that the cell
                       //is inactive
                    Fu[j-1+(i-1)*Ny] = 0.0;
                    NBids[j-1 +(i-1) * Ny] = 0;
                                       
                }
            }
        }
    }
}