// Automatically generated by FFC, the FEniCS Form Compiler, version 0.3.1.
// For further information, go to http://www/fenics.org/ffc/.
// Licensed under the GNU GPL Version 2.

#ifndef __POISSONSYSTEM_H
#define __POISSONSYSTEM_H

#include <dolfin/Mesh.h>
#include <dolfin/Cell.h>
#include <dolfin/Point.h>
#include <dolfin/Vector.h>
#include <dolfin/AffineMap.h>
#include <dolfin/FiniteElement.h>
#include <dolfin/FiniteElementSpec.h>
#include <dolfin/LinearForm.h>
#include <dolfin/BilinearForm.h>

namespace dolfin { namespace PoissonSystem {

/// This class contains the form to be evaluated, including
/// contributions from the interior and boundary of the domain.

class BilinearForm : public dolfin::BilinearForm
{
public:
  
  class TestElement : public dolfin::FiniteElement
  {
  public:
  
    TestElement() : dolfin::FiniteElement(), tensordims(0), subelements(0)
    {
      tensordims = new unsigned int [1];
      tensordims[0] = 2;
  
      // Element is simple, don't need to initialize subelements
    }
  
    ~TestElement()
    {
      if ( tensordims ) delete [] tensordims;
      if ( subelements )
      {
        for (unsigned int i = 0; i < elementdim(); i++)
          delete subelements[i];
        delete [] subelements;
      }
    }
  
    inline unsigned int spacedim() const
    {
      return 6;
    }
  
    inline unsigned int shapedim() const
    {
      return 2;
    }
  
    inline unsigned int tensordim(unsigned int i) const
    {
      dolfin_assert(i < 1);
      return tensordims[i];
    }
  
    inline unsigned int elementdim() const
    {
      return 1;
    }
  
    inline unsigned int rank() const
    {
      return 1;
    }
  
    void nodemap(int nodes[], const Cell& cell, const Mesh& mesh) const
    {
      nodes[0] = cell.vertexID(0);
      nodes[1] = cell.vertexID(1);
      nodes[2] = cell.vertexID(2);
      int offset = mesh.numVertices();
      nodes[3] = offset + cell.vertexID(0);
      nodes[4] = offset + cell.vertexID(1);
      nodes[5] = offset + cell.vertexID(2);
    }
  
    void pointmap(Point points[], unsigned int components[], const AffineMap& map) const
    {
      points[0] = map(0.000000000000000e+00, 0.000000000000000e+00);
      points[1] = map(1.000000000000000e+00, 0.000000000000000e+00);
      points[2] = map(0.000000000000000e+00, 1.000000000000000e+00);
      points[3] = map(0.000000000000000e+00, 0.000000000000000e+00);
      points[4] = map(1.000000000000000e+00, 0.000000000000000e+00);
      points[5] = map(0.000000000000000e+00, 1.000000000000000e+00);
      components[0] = 0;
      components[1] = 0;
      components[2] = 0;
      components[3] = 1;
      components[4] = 1;
      components[5] = 1;
    }
  
    void vertexeval(real values[], unsigned int vertex, const real x[], const Mesh& mesh) const
    {
      // FIXME: Temporary fix for Lagrange elements
      values[0] = x[vertex];
      int offset = mesh.numVertices();
      values[1] = x[offset + vertex];
    }
  
    const FiniteElement& operator[] (unsigned int i) const
    {
      return *this;
    }
  
    FiniteElement& operator[] (unsigned int i)
    {
      return *this;
    }
  
    FiniteElementSpec spec() const
    {
      FiniteElementSpec s("Vector Lagrange", "triangle", 1, 2);
      return s;
    }
    
  private:
  
    unsigned int* tensordims;
    FiniteElement** subelements;
  
  };
    
  class TrialElement : public dolfin::FiniteElement
  {
  public:
  
    TrialElement() : dolfin::FiniteElement(), tensordims(0), subelements(0)
    {
      tensordims = new unsigned int [1];
      tensordims[0] = 2;
  
      // Element is simple, don't need to initialize subelements
    }
  
    ~TrialElement()
    {
      if ( tensordims ) delete [] tensordims;
      if ( subelements )
      {
        for (unsigned int i = 0; i < elementdim(); i++)
          delete subelements[i];
        delete [] subelements;
      }
    }
  
    inline unsigned int spacedim() const
    {
      return 6;
    }
  
    inline unsigned int shapedim() const
    {
      return 2;
    }
  
    inline unsigned int tensordim(unsigned int i) const
    {
      dolfin_assert(i < 1);
      return tensordims[i];
    }
  
    inline unsigned int elementdim() const
    {
      return 1;
    }
  
    inline unsigned int rank() const
    {
      return 1;
    }
  
    void nodemap(int nodes[], const Cell& cell, const Mesh& mesh) const
    {
      nodes[0] = cell.vertexID(0);
      nodes[1] = cell.vertexID(1);
      nodes[2] = cell.vertexID(2);
      int offset = mesh.numVertices();
      nodes[3] = offset + cell.vertexID(0);
      nodes[4] = offset + cell.vertexID(1);
      nodes[5] = offset + cell.vertexID(2);
    }
  
    void pointmap(Point points[], unsigned int components[], const AffineMap& map) const
    {
      points[0] = map(0.000000000000000e+00, 0.000000000000000e+00);
      points[1] = map(1.000000000000000e+00, 0.000000000000000e+00);
      points[2] = map(0.000000000000000e+00, 1.000000000000000e+00);
      points[3] = map(0.000000000000000e+00, 0.000000000000000e+00);
      points[4] = map(1.000000000000000e+00, 0.000000000000000e+00);
      points[5] = map(0.000000000000000e+00, 1.000000000000000e+00);
      components[0] = 0;
      components[1] = 0;
      components[2] = 0;
      components[3] = 1;
      components[4] = 1;
      components[5] = 1;
    }
  
    void vertexeval(real values[], unsigned int vertex, const real x[], const Mesh& mesh) const
    {
      // FIXME: Temporary fix for Lagrange elements
      values[0] = x[vertex];
      int offset = mesh.numVertices();
      values[1] = x[offset + vertex];
    }
  
    const FiniteElement& operator[] (unsigned int i) const
    {
      return *this;
    }
  
    FiniteElement& operator[] (unsigned int i)
    {
      return *this;
    }
  
    FiniteElementSpec spec() const
    {
      FiniteElementSpec s("Vector Lagrange", "triangle", 1, 2);
      return s;
    }
    
  private:
  
    unsigned int* tensordims;
    FiniteElement** subelements;
  
  };
  
  BilinearForm() : dolfin::BilinearForm(0)
  {
    // Create finite element for test space
    _test = new TestElement();

    // Create finite element for trial space
    _trial = new TrialElement();
  }

  void eval(real block[], const AffineMap& map) const
  {
    // Compute geometry tensors
    const real G0_0_0 = map.det*map.g00*map.g00 + map.det*map.g01*map.g01;
    const real G0_0_1 = map.det*map.g00*map.g10 + map.det*map.g01*map.g11;
    const real G0_1_0 = map.det*map.g10*map.g00 + map.det*map.g11*map.g01;
    const real G0_1_1 = map.det*map.g10*map.g10 + map.det*map.g11*map.g11;
    const real G1_0_0 = map.det*map.g00*map.g00 + map.det*map.g01*map.g01;
    const real G1_0_1 = map.det*map.g00*map.g10 + map.det*map.g01*map.g11;
    const real G1_1_0 = map.det*map.g10*map.g00 + map.det*map.g11*map.g01;
    const real G1_1_1 = map.det*map.g10*map.g10 + map.det*map.g11*map.g11;

    // Compute element tensor
    block[0] = 4.999999999999998e-01*G0_0_0 + 4.999999999999997e-01*G0_0_1 + 4.999999999999997e-01*G0_1_0 + 4.999999999999996e-01*G0_1_1;
    block[1] = -4.999999999999998e-01*G0_0_0 - 4.999999999999997e-01*G0_1_0;
    block[2] = -4.999999999999997e-01*G0_0_1 - 4.999999999999996e-01*G0_1_1;
    block[3] = 0.000000000000000e+00;
    block[4] = 0.000000000000000e+00;
    block[5] = 0.000000000000000e+00;
    block[6] = -4.999999999999998e-01*G0_0_0 - 4.999999999999997e-01*G0_0_1;
    block[7] = 4.999999999999998e-01*G0_0_0;
    block[8] = 4.999999999999997e-01*G0_0_1;
    block[9] = 0.000000000000000e+00;
    block[10] = 0.000000000000000e+00;
    block[11] = 0.000000000000000e+00;
    block[12] = -4.999999999999997e-01*G0_1_0 - 4.999999999999996e-01*G0_1_1;
    block[13] = 4.999999999999997e-01*G0_1_0;
    block[14] = 4.999999999999996e-01*G0_1_1;
    block[15] = 0.000000000000000e+00;
    block[16] = 0.000000000000000e+00;
    block[17] = 0.000000000000000e+00;
    block[18] = 0.000000000000000e+00;
    block[19] = 0.000000000000000e+00;
    block[20] = 0.000000000000000e+00;
    block[21] = 4.999999999999998e-01*G1_0_0 + 4.999999999999997e-01*G1_0_1 + 4.999999999999997e-01*G1_1_0 + 4.999999999999996e-01*G1_1_1;
    block[22] = -4.999999999999998e-01*G1_0_0 - 4.999999999999997e-01*G1_1_0;
    block[23] = -4.999999999999997e-01*G1_0_1 - 4.999999999999996e-01*G1_1_1;
    block[24] = 0.000000000000000e+00;
    block[25] = 0.000000000000000e+00;
    block[26] = 0.000000000000000e+00;
    block[27] = -4.999999999999998e-01*G1_0_0 - 4.999999999999997e-01*G1_0_1;
    block[28] = 4.999999999999998e-01*G1_0_0;
    block[29] = 4.999999999999997e-01*G1_0_1;
    block[30] = 0.000000000000000e+00;
    block[31] = 0.000000000000000e+00;
    block[32] = 0.000000000000000e+00;
    block[33] = -4.999999999999997e-01*G1_1_0 - 4.999999999999996e-01*G1_1_1;
    block[34] = 4.999999999999997e-01*G1_1_0;
    block[35] = 4.999999999999996e-01*G1_1_1;
  }

  // No contribution from the boundary
  void eval(real block[], const AffineMap& map, unsigned int boundary) const {}   

};

/// This class contains the form to be evaluated, including
/// contributions from the interior and boundary of the domain.

class LinearForm : public dolfin::LinearForm
{
public:
  
  class TestElement : public dolfin::FiniteElement
  {
  public:
  
    TestElement() : dolfin::FiniteElement(), tensordims(0), subelements(0)
    {
      tensordims = new unsigned int [1];
      tensordims[0] = 2;
  
      // Element is simple, don't need to initialize subelements
    }
  
    ~TestElement()
    {
      if ( tensordims ) delete [] tensordims;
      if ( subelements )
      {
        for (unsigned int i = 0; i < elementdim(); i++)
          delete subelements[i];
        delete [] subelements;
      }
    }
  
    inline unsigned int spacedim() const
    {
      return 6;
    }
  
    inline unsigned int shapedim() const
    {
      return 2;
    }
  
    inline unsigned int tensordim(unsigned int i) const
    {
      dolfin_assert(i < 1);
      return tensordims[i];
    }
  
    inline unsigned int elementdim() const
    {
      return 1;
    }
  
    inline unsigned int rank() const
    {
      return 1;
    }
  
    void nodemap(int nodes[], const Cell& cell, const Mesh& mesh) const
    {
      nodes[0] = cell.vertexID(0);
      nodes[1] = cell.vertexID(1);
      nodes[2] = cell.vertexID(2);
      int offset = mesh.numVertices();
      nodes[3] = offset + cell.vertexID(0);
      nodes[4] = offset + cell.vertexID(1);
      nodes[5] = offset + cell.vertexID(2);
    }
  
    void pointmap(Point points[], unsigned int components[], const AffineMap& map) const
    {
      points[0] = map(0.000000000000000e+00, 0.000000000000000e+00);
      points[1] = map(1.000000000000000e+00, 0.000000000000000e+00);
      points[2] = map(0.000000000000000e+00, 1.000000000000000e+00);
      points[3] = map(0.000000000000000e+00, 0.000000000000000e+00);
      points[4] = map(1.000000000000000e+00, 0.000000000000000e+00);
      points[5] = map(0.000000000000000e+00, 1.000000000000000e+00);
      components[0] = 0;
      components[1] = 0;
      components[2] = 0;
      components[3] = 1;
      components[4] = 1;
      components[5] = 1;
    }
  
    void vertexeval(real values[], unsigned int vertex, const real x[], const Mesh& mesh) const
    {
      // FIXME: Temporary fix for Lagrange elements
      values[0] = x[vertex];
      int offset = mesh.numVertices();
      values[1] = x[offset + vertex];
    }
  
    const FiniteElement& operator[] (unsigned int i) const
    {
      return *this;
    }
  
    FiniteElement& operator[] (unsigned int i)
    {
      return *this;
    }
  
    FiniteElementSpec spec() const
    {
      FiniteElementSpec s("Vector Lagrange", "triangle", 1, 2);
      return s;
    }
    
  private:
  
    unsigned int* tensordims;
    FiniteElement** subelements;
  
  };
    
  class FunctionElement_0 : public dolfin::FiniteElement
  {
  public:
  
    FunctionElement_0() : dolfin::FiniteElement(), tensordims(0), subelements(0)
    {
      tensordims = new unsigned int [1];
      tensordims[0] = 2;
  
      // Element is simple, don't need to initialize subelements
    }
  
    ~FunctionElement_0()
    {
      if ( tensordims ) delete [] tensordims;
      if ( subelements )
      {
        for (unsigned int i = 0; i < elementdim(); i++)
          delete subelements[i];
        delete [] subelements;
      }
    }
  
    inline unsigned int spacedim() const
    {
      return 6;
    }
  
    inline unsigned int shapedim() const
    {
      return 2;
    }
  
    inline unsigned int tensordim(unsigned int i) const
    {
      dolfin_assert(i < 1);
      return tensordims[i];
    }
  
    inline unsigned int elementdim() const
    {
      return 1;
    }
  
    inline unsigned int rank() const
    {
      return 1;
    }
  
    void nodemap(int nodes[], const Cell& cell, const Mesh& mesh) const
    {
      nodes[0] = cell.vertexID(0);
      nodes[1] = cell.vertexID(1);
      nodes[2] = cell.vertexID(2);
      int offset = mesh.numVertices();
      nodes[3] = offset + cell.vertexID(0);
      nodes[4] = offset + cell.vertexID(1);
      nodes[5] = offset + cell.vertexID(2);
    }
  
    void pointmap(Point points[], unsigned int components[], const AffineMap& map) const
    {
      points[0] = map(0.000000000000000e+00, 0.000000000000000e+00);
      points[1] = map(1.000000000000000e+00, 0.000000000000000e+00);
      points[2] = map(0.000000000000000e+00, 1.000000000000000e+00);
      points[3] = map(0.000000000000000e+00, 0.000000000000000e+00);
      points[4] = map(1.000000000000000e+00, 0.000000000000000e+00);
      points[5] = map(0.000000000000000e+00, 1.000000000000000e+00);
      components[0] = 0;
      components[1] = 0;
      components[2] = 0;
      components[3] = 1;
      components[4] = 1;
      components[5] = 1;
    }
  
    void vertexeval(real values[], unsigned int vertex, const real x[], const Mesh& mesh) const
    {
      // FIXME: Temporary fix for Lagrange elements
      values[0] = x[vertex];
      int offset = mesh.numVertices();
      values[1] = x[offset + vertex];
    }
  
    const FiniteElement& operator[] (unsigned int i) const
    {
      return *this;
    }
  
    FiniteElement& operator[] (unsigned int i)
    {
      return *this;
    }
  
    FiniteElementSpec spec() const
    {
      FiniteElementSpec s("Vector Lagrange", "triangle", 1, 2);
      return s;
    }
    
  private:
  
    unsigned int* tensordims;
    FiniteElement** subelements;
  
  };
  
  LinearForm(Function& w0) : dolfin::LinearForm(1)
  {
    // Create finite element for test space
    _test = new TestElement();

    // Add functions
    add(w0, new FunctionElement_0());
  }

  void eval(real block[], const AffineMap& map) const
  {
    // Compute coefficients
    const real c0_0 = c[0][0];
    const real c0_1 = c[0][1];
    const real c0_2 = c[0][2];
    const real c0_3 = c[0][3];
    const real c0_4 = c[0][4];
    const real c0_5 = c[0][5];

    // Compute geometry tensors
    const real G0_0 = map.det*c0_0;
    const real G0_1 = map.det*c0_1;
    const real G0_2 = map.det*c0_2;
    const real G0_3 = map.det*c0_3;
    const real G0_4 = map.det*c0_4;
    const real G0_5 = map.det*c0_5;

    // Compute element tensor
    block[0] = 8.333333333333318e-02*G0_0 + 4.166666666666659e-02*G0_1 + 4.166666666666657e-02*G0_2;
    block[1] = 4.166666666666659e-02*G0_0 + 8.333333333333318e-02*G0_1 + 4.166666666666659e-02*G0_2;
    block[2] = 4.166666666666657e-02*G0_0 + 4.166666666666659e-02*G0_1 + 8.333333333333316e-02*G0_2;
    block[3] = 8.333333333333318e-02*G0_3 + 4.166666666666659e-02*G0_4 + 4.166666666666657e-02*G0_5;
    block[4] = 4.166666666666659e-02*G0_3 + 8.333333333333318e-02*G0_4 + 4.166666666666659e-02*G0_5;
    block[5] = 4.166666666666657e-02*G0_3 + 4.166666666666659e-02*G0_4 + 8.333333333333316e-02*G0_5;
  }

  // No contribution from the boundary
  void eval(real block[], const AffineMap& map, unsigned int boundary) const {}   

};

} }

#endif
