// This is a user-supplied routine that sets the the boundary conditions
//
void SetBndValues_Unst(const mesh& Mesh, dTensor3* q, dTensor3* aux)
{   
    int meqn = q->getsize(2);
    int kmax = q->getsize(3);
    int maux = aux->getsize(2);
    int      NumElems = Mesh.get_NumElems();
    int  NumPhysElems = Mesh.get_NumPhysElems();
    int NumGhostElems = Mesh.get_NumGhostElems();
    int      NumNodes = Mesh.get_NumNodes();
    int  NumPhysNodes = Mesh.get_NumPhysNodes();
    int      NumEdges = Mesh.get_NumEdges();

    // ----------------------------------------
    // Loop over each ghost cell element and
    // place the correct information into
    // these elements
    // ----------------------------------------
    for (int i=1; i<=NumGhostElems; i++)
    {
        int j = Mesh.get_ghost_link(i);

        for (int m=1; m<=meqn; m++)
            for (int k=1; k<=kmax; k++)
            {
                q->set(i+NumPhysElems,m,k, 0.0 );
            }
    }

}
// This is a user-supplied routine that sets the the boundary conditions
//
// The default routine for the 4D Vlasov code is to apply zero boundary
// conditions in configuration space.  This routine is identical to the one
// found in the unst branch of the 2D DoGPack code, and *should* be setting
// periodic boundary conditions.
//
void SetBndValues_Unst(const mesh& Mesh, dTensor3* q, dTensor3* aux)
{   

    // problem information (If this were to be pulled from DogParams, these
    // numbers would NOT be correct!  The reason is that each quadrature point
    // was actually saved as a separate "equation")
    const int meqn = q->getsize(2);
    const int kmax = q->getsize(3);
    const int maux = aux->getsize(2);

    // Mesh information
    const int      NumElems = Mesh.get_NumElems();
    const int  NumPhysElems = Mesh.get_NumPhysElems();
    const int NumGhostElems = Mesh.get_NumGhostElems();
    const int      NumNodes = Mesh.get_NumNodes();
    const int  NumPhysNodes = Mesh.get_NumPhysNodes();
    const int      NumEdges = Mesh.get_NumEdges();

    // ----------------------------------------
    // Loop over each ghost cell element and
    // place the correct information into
    // these elements
    // ----------------------------------------
    for (int i=1; i<=NumGhostElems; i++)
    {

        int j = Mesh.get_ghost_link(i);

        for (int m=1; m<=meqn; m++)
        for (int k=1; k<=kmax; k++)
        {
            q->set(i+NumPhysElems, m, k,  q->get(j, m, k) );
        }

    }

}
//
// Output basic mesh information to screen
//
void ScreenOutput(const mesh& Mesh)
{
  // Compute mesh quality parameters
  double totalarea = Mesh.get_area_prim(1);
  double maxarea = Mesh.get_area_prim(1);
  double minarea = Mesh.get_area_prim(1);
  for (int i=2; i<=Mesh.get_NumPhysElems(); i++)
    {
      double tmp = Mesh.get_area_prim(i);
      totalarea = totalarea + tmp;
      if (tmp < minarea)
	{ minarea = tmp; }
      if (tmp > maxarea)
	{ maxarea = tmp; }
    }

  double minAngle = 180.0;

  for (int i=1; i<=Mesh.get_NumPhysElems(); i++)
    {
      const int i1 = Mesh.get_tnode(i,1);
      const int i2 = Mesh.get_tnode(i,2);
      const int i3 = Mesh.get_tnode(i,3);

      point v12, v23, v31;
      v12.x = Mesh.get_node(i2,1) - Mesh.get_node(i1,1);
      v12.y = Mesh.get_node(i2,2) - Mesh.get_node(i1,2);
      v23.x = Mesh.get_node(i3,1) - Mesh.get_node(i2,1);
      v23.y = Mesh.get_node(i3,2) - Mesh.get_node(i2,2);
      v31.x = Mesh.get_node(i1,1) - Mesh.get_node(i3,1);
      v31.y = Mesh.get_node(i1,2) - Mesh.get_node(i3,2);
      
      double angle1 = acos((v12.x*-v31.x+v12.y*-v31.y)
			   /(sqrt(v12.x*v12.x+v12.y*v12.y)*sqrt(v31.x*v31.x+v31.y*v31.y)));
      double angle2 = acos((v23.x*-v12.x+v23.y*-v12.y)
			   /(sqrt(v23.x*v23.x+v23.y*v23.y)*sqrt(v12.x*v12.x+v12.y*v12.y)));
      double angle3 = acos((v31.x*-v23.x+v31.y*-v23.y)
			   /(sqrt(v31.x*v31.x+v31.y*v31.y)*sqrt(v23.x*v23.x+v23.y*v23.y)));
      if ((angle1*180/pi) < minAngle)
        {  minAngle = angle1*180/pi;  }
      if ((angle2*180/pi) < minAngle)
        {  minAngle = angle2*180/pi;  }
      if ((angle3*180/pi) < minAngle)
        {  minAngle = angle3*180/pi;  }
    }

  // Output summary of results to screen
  printf("\n");
  printf("  SUMMARY OF RESULTS:\n");
  printf("  -------------------\n");
  printf("          Number of Elements:  %8i\n",Mesh.get_NumElems());
  printf(" Number of Physical Elements:  %8i\n",Mesh.get_NumPhysElems());
  printf("    Number of Ghost Elements:  %8i\n",Mesh.get_NumGhostElems());
  printf("             Number of Nodes:  %8i\n",Mesh.get_NumNodes());
  printf("    Number of Physical Nodes:  %8i\n",Mesh.get_NumPhysNodes());
  printf("    Number of Boundary Nodes:  %8i\n",Mesh.get_NumBndNodes());
  printf("             Number of Edges:  %8i\n",Mesh.get_NumEdges());
  printf("    Number of Boundary Edges:  %8i\n",Mesh.get_NumBndEdges());
  printf("\n");
  printf("          Total Area Covered:  %24.16e\n",totalarea);
  printf("     Area Ratio: small/large:  %24.16e\n",minarea/maxarea);
  printf("    Angle Ratio: minAngle/60:  %24.16e\n",minAngle/60.0);
  printf("\n");
}
// This routine simply glues together many of the routines that are already
// written in the Poisson solver library
//
// phi( 1:SubNumPhysNodes       ) is a scalar quantity.  
//
// E1 ( 1:NumElems, 1:kmax2d ) is a vector quantity.
// E2 ( 1:NumElems, 1:kmax2d ) is a vector quantity.
//
// See also: ConvertEfieldOntoDGbasis
void ComputeElectricField( const double t, const mesh& Mesh, const dTensorBC5& q,
    dTensor2& E1, dTensor2& E2)
{

    //
    const int       mx = q.getsize(1);   assert_eq(mx,dogParamsCart2.get_mx());
    const int       my = q.getsize(2);   assert_eq(my,dogParamsCart2.get_my());
    const int NumElems = q.getsize(3);
    const int     meqn = q.getsize(4);
    const int     kmax = q.getsize(5);

    const int space_order = dogParams.get_space_order();

    // unstructured parameters:
    const int kmax2d    = E2.getsize(2);
    const int NumBndNodes  = Mesh.get_NumBndNodes();
    const int NumPhysNodes = Mesh.get_NumPhysNodes();

    // Quick error check
    if( !Mesh.get_is_submesh() )
    {
        printf("ERROR: mesh needs to have subfactor set to %d\n", space_order);
        printf("Go to Unstructured mesh and remesh the problem\n");
        exit(-1);
    }
    const int SubFactor    = Mesh.get_SubFactor();

    assert_eq( NumElems, Mesh.get_NumElems() );

    // -- Step 1: Compute rho -- //
    dTensor3 rho(NumElems, 1, kmax2d );
    void ComputeDensity( const mesh& Mesh, const dTensorBC5& q, dTensor3& rho );
    ComputeDensity( Mesh, q, rho );

    // -- Step 2: Figure out how large phi needs to be
    int SubNumPhysNodes = 0;
    int SubNumBndNodes  = 0;
    switch( dogParams.get_space_order() )
    {
        case 1:
            SubNumPhysNodes = NumPhysNodes;
            SubNumBndNodes  = NumBndNodes;
            break;

        case 2:
            SubNumPhysNodes = Mesh.get_SubNumPhysNodes();
            SubNumBndNodes  = Mesh.get_SubNumBndNodes();
            if(SubFactor!=2)
            {
                printf("\n");
                printf(" Error: for space_order = %i, need SubFactor = %i\n",space_order,2);
                printf("      SubFactor = %i\n",SubFactor);
                printf("\n");
                exit(1);
            }
            break;

        case 3:
            SubNumPhysNodes = Mesh.get_SubNumPhysNodes();
            SubNumBndNodes  = Mesh.get_SubNumBndNodes();
            if(SubFactor!=3)
            {
                printf("\n");
                printf(" Error: for space_order = %i, need SubFactor = %i\n",space_order,3);
                printf("      SubFactor = %i\n",SubFactor);
                printf("\n");
                exit(1);
            }
            break;

        default:
            printf("\n");
            printf(" ERROR in RunDogpack_unst.cpp: space_order value not supported.\n");
            printf("       space_order = %i\n",space_order);
            printf("\n");
            exit(1);
    }

    // local storage:
    dTensor1 rhs(SubNumPhysNodes);
    dTensor1 phi(SubNumPhysNodes);

    // Get Cholesky factorization matrix R
    //
    // TODO - this should be saved earlier in the code rather than reading
    // from file every time we with to run a Poisson solve!
    //
    SparseCholesky R(SubNumPhysNodes);
    string outputdir = dogParams.get_outputdir();
    R.init(outputdir);
    R.read(outputdir);

    // Create right-hand side vector
    void Rhs2D_unst(const int space_order,
            const mesh& Mesh, const dTensor3& rhs_dg,
            dTensor1& rhs);
    Rhs2D_unst(space_order, Mesh, rho, rhs);

    // Call Poisson solver  
    void PoissonSolver2D_unst(const int space_order,
            const mesh& Mesh,
            const SparseCholesky& R,
            const dTensor1& rhs,
            dTensor1& phi,
            dTensor2& E1,
            dTensor2& E2);
    PoissonSolver2D_unst(space_order, Mesh, R, rhs, phi, E1, E2);

    // Compare errors with the exact Electric field:
    //
    void L2Project_Unst(
        const double time,
        const dTensor2* vel_vec,
        const int istart, 
        const int iend, 
        const int QuadOrder, 
        const int BasisOrder_qin,
        const int BasisOrder_auxin,
        const int BasisOrder_fout,
        const mesh& Mesh, 
        const dTensor3* qin, 
        const dTensor3* auxin, 
        dTensor3* fout, 
        void (*Func)(const double t, const dTensor2* vel_vec, const dTensor2&,const dTensor2&,
            const dTensor2&,dTensor2&));

    const int sorder = dogParams.get_space_order();
    dTensor3 qtmp   (NumElems, 2, kmax2d );  qtmp.setall(0.);
    dTensor3 auxtmp (NumElems, 0, kmax2d );
    dTensor3 ExactE (NumElems, 2, kmax2d );
    L2Project_Unst( t, NULL, 1, NumElems, 
        sorder, sorder, sorder, sorder, Mesh, 
        &qtmp, &auxtmp, &ExactE, 
        &ExactElectricField );

    // Compute errors on these two:
    //
    double err = 0.;
    for( int n=1; n <= NumElems; n++ )
    for( int k=1; k <= kmax2d;   k++ )
    {
        err += Mesh.get_area_prim(n)*pow( ExactE.get(n,1,k) - E1.get(n,k), 2 );
        err += Mesh.get_area_prim(n)*pow( ExactE.get(n,2,k) - E2.get(n,k), 2 );
    }
    printf("error = %2.15e\n", err );

}