#include <stdio.h>
#include <cfloat>
#include <list>
#include <cstdlib>
#include <vector>
#include <math.h>
#include <iostream>
#include <algorithm>
#include <future>
#include <assert.h>
#include <boost/algorithm/clamp.hpp>
#include "tensorflow/core/framework/common_shape_fns.h"
#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor"
#include "tensorflow/core/framework/op.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/tensor_shape.h"
#include "tensorflow/core/util/work_sharder.h"

using namespace tensorflow;
using namespace std;
typedef Eigen::ThreadPoolDevice CPUDevice;
REGISTER_OP("CellEncodeLabel")
    .Attr("T: {int32, int64}")
 	.Attr("num_classes: int")
    .Input("tensor: T")
	.Output("tensor_o:T")
	.SetShapeFn([](shape_inference::InferenceContext* c) {
			const auto batch_size = c->Dim(c->input(0),0);
            int num_classes = 0;
            c->GetAttr("num_classes",&num_classes);
            auto shape = c->MakeShape({batch_size,num_classes});
            c->set_output(0,shape);
    return Status::OK();
    });

template <typename Device, typename T>
class CellEncodeLabelOp: public OpKernel {
	public:
		explicit CellEncodeLabelOp(OpKernelConstruction* context) : OpKernel(context) {
            OP_REQUIRES_OK(context, context->GetAttr("num_classes", &num_classes_));
            //第一级分鳞，腺，TRI,CC,....
            //0-7表示第一级
            label_map_level0_[1] = 0;
            label_map_level0_[2] = 0;
            label_map_level0_[3] = 2;
            label_map_level0_[4] = 3;
            label_map_level0_[5] = 1;
            label_map_level0_[6] = 4;
            label_map_level0_[7] = 5;
            label_map_level0_[8] = 0;
            label_map_level0_[9] = 6;
            label_map_level0_[10] = 7;
            label_map_level0_[11] = 0;
            label_map_level0_[12] = 0;
            //第二级分低级别鳞状病变,高级别鳞状病变
            //8-9表示第二级
            label_map_level1_[1] = 9;
            label_map_level1_[11] = 9;
            label_map_level1_[2] = 8;
            label_map_level1_[8] = 8;
            label_map_level1_[12] = 8;
            //第三级分（ASCUS,LSIL0,(ASCH,HSIL,SCC)
            //10-14表示第三级
            label_map_level2_[1] = 11;
            label_map_level2_[11] = 10;
            label_map_level2_[2] = 13;
            label_map_level2_[8] = 14;
            label_map_level2_[12] = 12;
		}

		void Compute(OpKernelContext* context) override
        {
            const Tensor &_tensor     = context->input(0);
            auto          tensor_flat = _tensor.flat<T>().data();
            const auto    batch_size  = _tensor.dim_size(0);
            const auto    data_nr     = _tensor.NumElements();

			OP_REQUIRES(context, _tensor.dims() == 1, errors::InvalidArgument("input must be 1-dimensional"));

            int dims_2d[] = {batch_size,num_classes_};

            TensorShape outshape;
            TensorShapeUtils::MakeShape(dims_2d, 2, &outshape);

            Tensor      *output_tensor = nullptr;

            OP_REQUIRES_OK(context,context->allocate_output(0,outshape,&output_tensor));

            auto o_tensor = output_tensor->template tensor<T,2>();

            o_tensor.setZero();

            for(auto i=0; i<data_nr; ++i) {
                const auto l = tensor_flat[i];

                if(0 == l)
                    continue;

                auto kt = label_map_level0_.find(l);
                if(kt == label_map_level0_.end()) {
                    cout<<"Error label "<<l<<endl;
                    continue;
                }

                o_tensor(i,kt->second) = 1;

                auto it = label_map_level1_.find(l);

                if(label_map_level1_.end() == it)
                    continue;
                o_tensor(i,it->second) = 1;
                auto jt = label_map_level2_.find(l);
                if(label_map_level2_.end() == jt)
                    continue;
                o_tensor(i,jt->second) = 1;
            }
        }
	private:
        int num_classes_ = 0;
        unordered_map<int,int> label_map_level0_;
        unordered_map<int,int> label_map_level1_;
        unordered_map<int,int> label_map_level2_;
};
REGISTER_KERNEL_BUILDER(Name("CellEncodeLabel").Device(DEVICE_CPU).TypeConstraint<int>("T"), CellEncodeLabelOp<CPUDevice, int>);
REGISTER_KERNEL_BUILDER(Name("CellEncodeLabel").Device(DEVICE_CPU).TypeConstraint<tensorflow::int64>("T"), CellEncodeLabelOp<CPUDevice, tensorflow::int64>);

REGISTER_OP("CellDecodeLabel")
    .Attr("T: {float, double}")
    .Input("tensor: T") //probability
	.Output("tensor_o:T")
	.SetShapeFn(shape_inference::UnchangedShape);

template <typename Device, typename T>
class CellDecodeLabelOp: public OpKernel {
	public:
		explicit CellDecodeLabelOp(OpKernelConstruction* context) : OpKernel(context) {
            //第一级分鳞，腺，TRI,CC,....
            //0-7表示第一级
            label_map_level0_[1] = 5;
            label_map_level0_[2] = 3;
            label_map_level0_[3] = 4;
            label_map_level0_[4] = 6;
            label_map_level0_[5] = 7;
            label_map_level0_[6] = 9;
            label_map_level0_[7] = 10;
		}

		void Compute(OpKernelContext* context) override
        {
            const Tensor &_tensor    = context->input(0);

			OP_REQUIRES(context, _tensor.dims() == 2, errors::InvalidArgument("input must be 2-dimensional"));

            auto          tensor     = _tensor.template tensor<T,2>();
            const auto    batch_size = _tensor.dim_size(0);
            const auto    C          = _tensor.dim_size(1);
            const auto    kThreadNr  = 512;

            TensorShape  output_shape  = _tensor.shape();
            Tensor      *output_tensor = nullptr;

            OP_REQUIRES_OK(context,context->allocate_output(0,output_shape,&output_tensor));

            auto o_tensor = output_tensor->template tensor<T,2>();
            auto i_data = _tensor.template flat<T>().data();

            o_tensor.setZero();

            auto fn = [&](int begin,int end) {
                for(auto i=begin; i<end; ++i) {
                    for(auto it=label_map_level0_.begin(); it!=label_map_level0_.end(); ++it) {
                        o_tensor(i,it->second) = tensor(i,it->first);
                    }
                    if(tensor(i,8)>=tensor(i,9)) { //H
                        auto d = i_data+i*C+12;
                        auto it = max_element(d,d+3);
                        auto dis = it-d;
                        //auto p = max<T>(d[dis],tensor(i,0));
                        //auto p = tensor(i,0);
                        auto p = min<T>(d[dis],tensor(i,0));

                        if(dis==0){
                            o_tensor(i,12) = p;
                        } else if(dis==1) {
                            o_tensor(i,2) = p;
                        } else {
                            o_tensor(i,8) = p;
                        }
                    } else {
                        if(tensor(i,10)>=tensor(i,11)) {
                            //o_tensor(i,11) = max<T>(tensor(i,0),tensor(i,10));
                            //o_tensor(i,11) = tensor(i,0);
                            o_tensor(i,11) = min<T>(tensor(i,0),tensor(i,10));
                        } else {
                            //o_tensor(i,1) = max<T>(tensor(i,0),tensor(i,11));
                            //o_tensor(i,1) = tensor(i,0);
                            o_tensor(i,1) = min<T>(tensor(i,0),tensor(i,11));
                        }
                    }
                }
            };

            list<future<void>> furs;

            for(auto i=0; i<batch_size; i += kThreadNr) {
                furs.push_back(std::async(std::launch::async,fn,i,min<int>(i+kThreadNr,batch_size)));
            }
        }
	private:
        unordered_map<int,int> label_map_level0_;
};
REGISTER_KERNEL_BUILDER(Name("CellDecodeLabel").Device(DEVICE_CPU).TypeConstraint<float>("T"), CellDecodeLabelOp<CPUDevice, float>);
REGISTER_KERNEL_BUILDER(Name("CellDecodeLabel").Device(DEVICE_CPU).TypeConstraint<double>("T"), CellDecodeLabelOp<CPUDevice, double>);

typedef Eigen::ThreadPoolDevice CPUDevice;
REGISTER_OP("CellEncodeLabel2")
    .Attr("T: {int32, int64}")
    .Input("tensor: T")
	.Output("tensor_o0:T")
	.Output("tensor_o1:T")
	.SetShapeFn([](shape_inference::InferenceContext* c) {
			const auto batch_size = c->Dim(c->input(0),0);
            int num_classes = 0;
            auto shape = c->MakeShape({batch_size});
            c->set_output(0,shape);
            auto shape1 = c->MakeShape({batch_size});
            c->set_output(1,shape1);
    return Status::OK();
    });

template <typename Device, typename T>
class CellEncodeLabel2Op: public OpKernel {
	public:
		explicit CellEncodeLabel2Op(OpKernelConstruction* context) : OpKernel(context) {
            //第一级分鳞，腺，TRI,CC,....
            //0-8表示第一级, 0 表示背景
            label_map_level0_[1] = 8;
            label_map_level0_[2] = 8;
            label_map_level0_[3] = 2;
            label_map_level0_[4] = 3;
            label_map_level0_[5] = 1;
            label_map_level0_[6] = 4;
            label_map_level0_[7] = 5;
            label_map_level0_[8] = 8;
            label_map_level0_[9] = 6;
            label_map_level0_[10] = 7;
            label_map_level0_[11] = 8;
            label_map_level0_[12] = 8;
            //第二级分低级别鳞状病变,高级别鳞状病变
            //8-9表示第二级
            label_map_level1_[11] = 0;
            label_map_level1_[1] = 1;
            label_map_level1_[12] = 3; //增加了LSIL与ASCH之间的距离
            label_map_level1_[2] = 4;
            label_map_level1_[8] = 5;
		}

		void Compute(OpKernelContext* context) override
        {
            const Tensor &_tensor     = context->input(0);
            auto          tensor_flat = _tensor.flat<T>().data();
            const auto    batch_size  = _tensor.dim_size(0);
            const auto    data_nr     = _tensor.NumElements();

			OP_REQUIRES(context, _tensor.dims() == 1, errors::InvalidArgument("input must be 1-dimensional"));

            int dims_1d0[] = {batch_size};
            int dims_1d1[] = {batch_size};

            TensorShape outshape0;
            TensorShape outshape1;
            TensorShapeUtils::MakeShape(dims_1d0, 1, &outshape0);
            TensorShapeUtils::MakeShape(dims_1d1, 1, &outshape1);

            Tensor      *output_tensor0 = nullptr;
            Tensor      *output_tensor1 = nullptr;

            OP_REQUIRES_OK(context,context->allocate_output(0,outshape0,&output_tensor0));
            OP_REQUIRES_OK(context,context->allocate_output(1,outshape1,&output_tensor1));

            auto o_tensor0 = output_tensor0->template tensor<T,1>();
            auto o_tensor1 = output_tensor1->template tensor<T,1>();

            o_tensor0.setZero();
            o_tensor1.setZero();

            for(auto i=0; i<data_nr; ++i) {
                const auto l = tensor_flat[i];
                auto tp = label_map_level0_[l];

                o_tensor0(i) = tp;
                if(8 == tp) {
                    auto it = label_map_level1_.find(l);

                    if(label_map_level1_.end() == it) {
                        cout<<"Error label "<<l<<endl;
                        continue;
                    }
                    o_tensor1(i) = it->second;
                } else {
                    o_tensor1(i) = -1; //如果不是鳞状病变，默认为-1
                }
            }
        }
	private:
        unordered_map<int,int> label_map_level0_;
        unordered_map<int,int> label_map_level1_;
};
REGISTER_KERNEL_BUILDER(Name("CellEncodeLabel2").Device(DEVICE_CPU).TypeConstraint<int>("T"), CellEncodeLabel2Op<CPUDevice, int>);
REGISTER_KERNEL_BUILDER(Name("CellEncodeLabel2").Device(DEVICE_CPU).TypeConstraint<tensorflow::int64>("T"), CellEncodeLabel2Op<CPUDevice, tensorflow::int64>);

REGISTER_OP("CellDecodeLabel2")
    .Attr("T: {float, double}")
 	.Attr("num_classes: int")
    .Input("tensor0: T") //probability
    .Input("tensor1: T") //regs
	.Output("tensor_o:T")
	.SetShapeFn([](shape_inference::InferenceContext* c) {
			const auto batch_size = c->Dim(c->input(0),0);
            int num_classes = 0;
            c->GetAttr("num_classes",&num_classes);
            auto shape = c->MakeShape({batch_size,num_classes});
            c->set_output(0,shape);
    return Status::OK();
    });

template <typename Device, typename T>
class CellDecodeLabel2Op: public OpKernel {
	public:
		explicit CellDecodeLabel2Op(OpKernelConstruction* context) : OpKernel(context) {
            OP_REQUIRES_OK(context, context->GetAttr("num_classes", &num_classes_));
            //第一级分鳞，腺，TRI,CC,....
            //0-7表示第一级
            label_map_level0_[1] = 5;
            label_map_level0_[2] = 3;
            label_map_level0_[3] = 4;
            label_map_level0_[4] = 6;
            label_map_level0_[5] = 7;
            label_map_level0_[6] = 9;
            label_map_level0_[7] = 10;
            //
            label_map_level1_[0] = 11;
            label_map_level1_[1] = 1;
            label_map_level1_[3] = 12;
            label_map_level1_[4] = 2;
            label_map_level1_[5] = 8;
		}

		void Compute(OpKernelContext* context) override
        {
            const Tensor &_tensor0    = context->input(0);
            const Tensor &_tensor1    = context->input(1);

			OP_REQUIRES(context, _tensor0.dims() == 2, errors::InvalidArgument("input0 must be 2-dimensional"));
			OP_REQUIRES(context, _tensor1.dims() == 2, errors::InvalidArgument("input1 must be 2-dimensional"));

            auto          tensor0     = _tensor0.template tensor<T,2>();
            auto          tensor1     = _tensor1.template tensor<T,2>();
            const auto    batch_size = _tensor0.dim_size(0);
            const auto    kThreadNr  = 512;

            int dims_2d[] = {batch_size,num_classes_};
            TensorShape outshape;
            Tensor      *output_tensor = nullptr;

            TensorShapeUtils::MakeShape(dims_2d, 2, &outshape);

            OP_REQUIRES_OK(context,context->allocate_output(0,outshape,&output_tensor));

            auto o_tensor = output_tensor->template tensor<T,2>();

            o_tensor.setZero();

            auto fn = [&](int begin,int end) {
                for(auto i=begin; i<end; ++i) {
                    for(auto it=label_map_level0_.begin(); it!=label_map_level0_.end(); ++it) {
                        o_tensor(i,it->second) = tensor0(i,it->first);
                    }
                    auto index = int(tensor1(i,0)+0.5);
                    float scale = 1.0;
                    if(2 == index) {
                        index = int(tensor1(i,0));
                        if(2 == index)
                            index = 3;
                    } else if(index<=-1) {
                        scale = 0.5;
                    }
                    index = max(min(index,5),0);
                    auto idx = label_map_level1_[index];
                    o_tensor(i,idx) = tensor0(i,8)*scale;
                }
            };

            list<future<void>> furs;

            for(auto i=0; i<batch_size; i += kThreadNr) {
                furs.push_back(std::async(std::launch::async,fn,i,min<int>(i+kThreadNr,batch_size)));
            }
        }
	private:
        unordered_map<int,int> label_map_level0_;
        unordered_map<int,int> label_map_level1_;
        int num_classes_ = 0;
};
REGISTER_KERNEL_BUILDER(Name("CellDecodeLabel2").Device(DEVICE_CPU).TypeConstraint<float>("T"), CellDecodeLabel2Op<CPUDevice, float>);
REGISTER_KERNEL_BUILDER(Name("CellDecodeLabel2").Device(DEVICE_CPU).TypeConstraint<double>("T"), CellDecodeLabel2Op<CPUDevice, double>);


#ifndef PROPOSALTARGET_OP_HPP
#define PROPOSALTARGET_OP_HPP

#include "bbox.h"
#include "proposaltarget_op.h"

#include <Eigen/Dense>
#include <tuple>

namespace mxnet {
namespace op {

template <typename xpu>
class ProposalTargetOp : public Operator {
 public:
  explicit ProposalTargetOp(ProposalTargetParam param) { this->param_ = param; }

  virtual void Forward(const OpContext& ctx,
                       const std::vector<TBlob>& in_data,
                       const std::vector<OpReqType>& req,
                       const std::vector<TBlob>& out_data,
                       const std::vector<TBlob>& /*aux_states*/) override {
    using namespace mshadow;
    using namespace mshadow::expr;
    CHECK_EQ(param_.batch_images, in_data[1].shape_[0]);
    using namespace mshadow;
    mshadow::Stream<xpu>* s = ctx.get_stream<xpu>();

    // get input tensors
    // rois [n, 5] (batch_index, x1, y1, x2, y2)
    Tensor<xpu, 2> all_rois_x = in_data[0].get<xpu, 2, real_t>(s);
    // gt_boxes [b, n, 5] (x1, y1, x2, y2, cls)
    Tensor<xpu, 3> all_gt_boxes_x = in_data[1].get<xpu, 3, real_t>(s);

    // define result shapes
    auto rois_shape = Shape2(static_cast<index_t>(param_.batch_rois), 5);
    auto label_shape = Shape1(static_cast<index_t>(param_.batch_rois));
    auto bbox_target_shape =
        Shape2(static_cast<index_t>(param_.batch_rois),
               static_cast<index_t>(param_.num_classes * 4));
    auto bbox_weight_shape =
        Shape2(static_cast<index_t>(param_.batch_rois),
               static_cast<index_t>(param_.num_classes * 4));

    // ---------------- Main logic - cpu version ----------------------------
    auto rois_per_image = param_.batch_rois / param_.batch_images;
    auto fg_rois_per_image =
        static_cast<int>(std::round(param_.fg_fraction * rois_per_image));
    std::vector<float> box_stds;
    box_stds.assign(param_.box_stds.begin(), param_.box_stds.end());

    using EigenMatrix =
        Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
    EigenMatrix rois(rois_shape[0], rois_shape[1]);
    EigenMatrix labels(label_shape[0], 1);
    EigenMatrix bbox_targets(bbox_target_shape[0], bbox_target_shape[1]);
    EigenMatrix bbox_weights(bbox_weight_shape[0], bbox_weight_shape[1]);

    EigenMatrix all_rois(all_rois_x.shape_[0], all_rois_x.shape_[1]);
    Tensor<cpu, 2, real_t> all_rois_t(all_rois.data(), all_rois_x.shape_);
    Copy(all_rois_t, all_rois_x, s);

    EigenMatrix batch_gt_boxes(all_gt_boxes_x.shape_[1],
                               all_gt_boxes_x.shape_[2]);
    for (int batch_idx = 0; batch_idx < param_.batch_images; ++batch_idx) {
      //----------------------------------------------
      // select gt boxes related to the current batch element
      Tensor<cpu, 2, real_t> all_gt_boxes_t(
          batch_gt_boxes.data(),
          Shape2(all_gt_boxes_x.shape_[1], all_gt_boxes_x.shape_[2]));
      Copy(all_gt_boxes_t, all_gt_boxes_x[static_cast<index_t>(batch_idx)], s);

      // select gt boxes with foreground class
      auto gt_boxes_count = (batch_gt_boxes.col(4).array() > 0).count();
      Eigen::MatrixXf gt_boxes(gt_boxes_count, all_gt_boxes_x.shape_[2]);
      for (Eigen::Index i = 0, j = 0; i < batch_gt_boxes.rows();
           ++i) {  // col major
        if (batch_gt_boxes(i, 4) > 0)
          gt_boxes.row(j++) = batch_gt_boxes.row(i);
      }
      //----------------------------------------------
      // select rois related to the current batch element, images have different
      // size so rois count can also be different
      auto batch_rois_count =
          (all_rois.col(0).array() == static_cast<float>(batch_idx)).count();

      Eigen::MatrixXf batch_rois(
          batch_rois_count,
          all_rois.cols() - 1);  // exclude batch index col
      for (Eigen::Index i = 0, j = 0; i < all_rois.rows(); ++i) {  // col major
        if (static_cast<int>(all_rois(i, 0)) == batch_idx)
          batch_rois.row(j++) = all_rois.row(i).rightCols(4);
      }

      //----------------------------------------------
      // Include ground-truth boxes in the set of candidate rois
      {
        Eigen::MatrixXf rois_and_gt(batch_rois.rows() + gt_boxes.rows(),
                                    rois.cols() - 1);
        rois_and_gt << batch_rois,
            gt_boxes.block(0, 0, gt_boxes.rows(), gt_boxes.cols() - 1);
        batch_rois = rois_and_gt;
      }

      //----------------------------------------------
      // mark rois for this batch
      rois.block(batch_idx * rois_per_image, 0, rois_per_image, 1).array() =
          batch_idx;

      //----------------------------------------------
      // generate random sample of ROIs comprising foreground and background
      // examples
      auto b_rois = rois.block(batch_idx * rois_per_image, 1, rois_per_image,
                               rois.cols() - 1);
      auto b_labels = labels.block(batch_idx * rois_per_image, 0,
                                   rois_per_image, labels.cols());
      auto b_bbox_targets = bbox_targets.block(
          batch_idx * rois_per_image, 0, rois_per_image, bbox_targets.cols());
      auto b_bbox_weights = bbox_weights.block(
          batch_idx * rois_per_image, 0, rois_per_image, bbox_weights.cols());
      std::tie(b_rois, b_labels, b_bbox_targets, b_bbox_weights) =
          SampleRois(batch_rois, gt_boxes, param_.num_classes, rois_per_image,
                     fg_rois_per_image, param_.fg_overlap, box_stds);
    }

    Tensor<cpu, 2, real_t> rois_t(rois.data(), rois_shape);
    Tensor<cpu, 1, real_t> labels_t(labels.data(), label_shape);
    Tensor<cpu, 2, real_t> bbox_targets_t(bbox_targets.data(),
                                          bbox_target_shape);
    Tensor<cpu, 2, real_t> bbox_weights_t(bbox_weights.data(),
                                          bbox_weight_shape);

    // ---------------- Main logic end --------------------------

    // get destination tensors
    Tensor<xpu, 2> out0 =
        out_data[0].get_with_shape<xpu, 2, real_t>(rois_shape, s);
    Tensor<xpu, 1> out1 =
        out_data[1].get_with_shape<xpu, 1, real_t>(label_shape, s);
    Tensor<xpu, 2> out2 =
        out_data[2].get_with_shape<xpu, 2, real_t>(bbox_target_shape, s);
    Tensor<xpu, 2> out3 =
        out_data[3].get_with_shape<xpu, 2, real_t>(bbox_weight_shape, s);

    // copy results
    for (auto r : req) {
      CHECK_EQ(kWriteTo, r);
    }

    Copy(out0, rois_t, s);
    Copy(out1, labels_t, s);
    Copy(out2, bbox_targets_t, s);
    Copy(out3, bbox_weights_t, s);
  }

  void Backward(const OpContext& ctx,
                const std::vector<TBlob>& /*out_grad*/,
                const std::vector<TBlob>& /*in_data*/,
                const std::vector<TBlob>& /*out_data*/,
                const std::vector<OpReqType>& req,
                const std::vector<TBlob>& in_grad,
                const std::vector<TBlob>& /*aux_states*/) override {
    using namespace mshadow;
    using namespace mshadow::expr;
    CHECK_EQ(in_grad.size(), 2);

    Stream<xpu>* s = ctx.get_stream<xpu>();
    auto grad0 = in_grad[0].get<xpu, 2, real_t>(s);
    auto grad1 = in_grad[1].get<xpu, 3, real_t>(s);

    // can not assume the grad would be zero
    Assign(grad0, req[0], 0);
    Assign(grad1, req[1], 0);
  }

 private:
  ProposalTargetParam param_;
};  // class ProposalOp

}  // namespace op
}  // namespace mxnet
#endif  // PROPOSALTARGET_OP_HPP


/* Copyright Institute of Sound and Vibration Research - All rights reserved */

#include <libsignalflows/baseline_renderer.hpp>

#include <libefl/basic_matrix.hpp>

#include <libpanning/LoudspeakerArray.h>

#include <libvisr/signal_flow_context.hpp>

#include <boost/test/unit_test.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>

#include <ciso646>
#include <iostream>
#include <stdexcept>
#include <string>
#include <sstream>

namespace visr
{
namespace objectmodel
{
namespace test
{

BOOST_AUTO_TEST_CASE( InstantiateRenderer )
{
  boost::filesystem::path const arrayConfigFile( CMAKE_SOURCE_DIR "/config/generic/bs2051-9+10+3.xml" );
  BOOST_CHECK( exists( arrayConfigFile ) and not is_directory( arrayConfigFile ) );

  panning::LoudspeakerArray arrayConfig;
  arrayConfig.loadXmlFile( arrayConfigFile.string() );

  std::size_t const numberOfInputs = 16;
  std::size_t const numberOfOutputs = 40;
  std::size_t const period = 128;
  std::size_t const interpolationPeriod = 16 * period;
  std::size_t const numRealLoudspeakers = arrayConfig.getNumRegularSpeakers();
  std::size_t const diffusionFilterLength = 512;
  efl::BasicMatrix<SampleType> const diffusionFilters( numRealLoudspeakers, diffusionFilterLength );

  std::string const trackingConfig( "" );

  // Construct the reverb configuration
  std::size_t const numReverbObjects = 5;
  std::size_t const discreteReflectionsPerObject = 4;
  double const lateFilterLengthSeconds = 0.05;
  std::string const lateDiffusionFilters( CMAKE_SOURCE_DIR "/config/filters/random_phase_allpass_64ch_512taps.wav" );
  double const maximumDiscreteReflectionDelay = 0.23456f;

  std::size_t const numInputEqSections = 0;

  std::stringstream reverbConfig;
  reverbConfig << "{ \"numReverbObjects\": " << numReverbObjects
               << ", \"discreteReflectionsPerObject\": " << discreteReflectionsPerObject
               << ", \"lateReverbFilterLength\": " << lateFilterLengthSeconds
               << ", \"maxDiscreteReflectionDelay\": " << maximumDiscreteReflectionDelay
               << ", \"lateReverbDecorrelationFilters\": \"" << lateDiffusionFilters
               << "\" }";

  SignalFlowContext context( period, 48000 );

  signalflows::BaselineRenderer( context, "", nullptr,
                                 arrayConfig,
                                 numberOfInputs,
                                 numberOfOutputs,
                                 interpolationPeriod,
                                 diffusionFilters,
                                 trackingConfig,
                                 8888,
                                 numInputEqSections,
                                 reverbConfig.str(),
                                 false // No frequency-dependent panning.
                                 );
}

} // namespace test
} // namespace objectmodel
} // namespce visr


// Copyright (c) 2012 Christopher Lux <christopherlux@gmail.com>
// Distributed under the Modified BSD License, see license.txt.

#include "highres_time_stamp.h"

#include <cassert>

#include <boost/cast.hpp>

#include <scm/log.h>

namespace scm {
namespace time {
namespace detail {

high_res_time_stamp::~high_res_time_stamp()
{
}

inline time_duration high_res_time_stamp::get_overhead() const
{
    return (_overhead);
}

#if SCM_PLATFORM == SCM_PLATFORM_WINDOWS

#include <scm/core/platform/windows.h>

namespace
{

time_stamp get_pfc_frequency()
{
    LARGE_INTEGER       frequency;

    if (!QueryPerformanceFrequency(&frequency)) {
        char* error_msg;

        FormatMessage(  FORMAT_MESSAGE_IGNORE_INSERTS
                      | FORMAT_MESSAGE_FROM_SYSTEM
                      | FORMAT_MESSAGE_ALLOCATE_BUFFER,
                      0,
                      GetLastError(),
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                      (LPTSTR)&error_msg,
                      1024,
                      0);

        scm::err() << log::error
                   << "high_res_time_stamp::initialize(): "
                   << "error obtaining performance counter frequency" << log::nline
                   << " - system error message: " << log::nline << "    "
                   << error_msg
                   << log::end;

        LocalFree(error_msg);

        return (false);
    }

    return (boost::numeric_cast<time_stamp>(frequency.QuadPart));
}

time_stamp      pfc_frequency = time_stamp(0);

} // namespace

high_res_time_stamp::high_res_time_stamp()
    : _overhead(time_duration(0, 0, 0, 0))
{
    pfc_frequency = get_pfc_frequency();
}

bool high_res_time_stamp::initialize()
{
    // calculate overhead of 'now' funktion

    return (true);
}

time_stamp high_res_time_stamp::ticks_per_second()
{
    return (pfc_frequency);
}

time_stamp high_res_time_stamp::now()
{
    static LARGE_INTEGER       current_time_counter;

    if (!QueryPerformanceCounter(&current_time_counter)) {
        char* error_msg;

        FormatMessage(  FORMAT_MESSAGE_IGNORE_INSERTS
                      | FORMAT_MESSAGE_FROM_SYSTEM
                      | FORMAT_MESSAGE_ALLOCATE_BUFFER,
                      0,
                      GetLastError(),
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                      (LPTSTR)&error_msg,
                      1024,
                      0);

        scm::err() << log::error
                   << "high_res_time_stamp::now(): "
                   << "error obtaining performance counter" << log::nline
                   << " - system error message: " << log::nline << "    "
                   << error_msg
                   << log::end;

        LocalFree(error_msg);

        return (0);
    }

    return (boost::numeric_cast<time_stamp>(current_time_counter.QuadPart));
}

#elif    SCM_PLATFORM == SCM_PLATFORM_LINUX \
      || SCM_PLATFORM == SCM_PLATFORM_APPLE

#include <ctime>

high_res_time_stamp::high_res_time_stamp()
    : _overhead(time_duration(0, 0, 0, 0))
{
}

bool high_res_time_stamp::initialize()
{
    // calculate overhead of 'now' funktion

    return (true);
}

time_stamp high_res_time_stamp::ticks_per_second()
{
    return (1000000000);
}

time_stamp high_res_time_stamp::now()
{
    static timespec current_time;

    clock_gettime(CLOCK_MONOTONIC, &current_time);

    return (  boost::numeric_cast<time_stamp>(current_time.tv_sec) * 1000000000
            + boost::numeric_cast<time_stamp>(current_time.tv_nsec));
}

#endif // SCM_PLATFORM == SCM_PLATFORM_WINDOWS

} // namespace detail
} // namespace time
} // namespace scm


#include "base.hpp"
#include "constants.hpp"

#include <boost/math/special_functions/round.hpp>
#include <cmath>
#include <jngl.hpp>
#include <sstream>
#include <thread>

JNGL_MAIN_BEGIN {
	jngl::setScaleFactor(
	    std::floor(std::min(jngl::getDesktopWidth() / 800, jngl::getDesktopHeight() / 600)));
	jngl::showWindow("Bike", boost::math::iround(screenWidth * jngl::getScaleFactor()),
	                 boost::math::iround(screenHeight * jngl::getScaleFactor()));
	Base base;
	double oldTime = jngl::getTime();
	bool needDraw = true;
	while (jngl::running()) {
		if (jngl::getTime() - oldTime > timePerFrame) {
			// This stuff needs to be done 100 times per second
			oldTime += timePerFrame;
			needDraw = true;
			base.DoFrame();
		} else {
			if (needDraw) {
				needDraw = false;
				// This needs to be done when "needDraw" is true
				jngl::updateInput();
				jngl::translate(-jngl::getScreenWidth() / 2.0, -jngl::getScreenHeight() / 2.0);
				base.Draw();
				jngl::swapBuffers();
			} else {
				// Nothing to do? Okay let's Sleep.
				std::this_thread::sleep_for(std::chrono::milliseconds(10));
			}
		}
	}
} JNGL_MAIN_END


// Copyright (C) 2011-2012 by the BEM++ Authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "grid_function.hpp"

#include "abstract_boundary_operator_pseudoinverse.hpp"
#include "assembly_options.hpp"
#include "boundary_operator.hpp"
#include "context.hpp"
#include "discrete_boundary_operator.hpp"
#include "identity_operator.hpp"
#include "local_assembler_construction_helper.hpp"

#include "../common/complex_aux.hpp"
#include "../common/deprecated.hpp"
#include "../fiber/collection_of_3d_arrays.hpp"
#include "../fiber/basis.hpp"
#include "../fiber/basis_data.hpp"
#include "../fiber/collection_of_basis_transformations.hpp"
#include "../fiber/explicit_instantiation.hpp"
#include "../fiber/function.hpp"
#include "../fiber/local_assembler_for_grid_functions.hpp"
#include "../fiber/opencl_handler.hpp"
#include "../fiber/quadrature_strategy.hpp"
#include "../fiber/raw_grid_geometry.hpp"
#include "../grid/geometry_factory.hpp"
#include "../grid/grid.hpp"
#include "../grid/grid_view.hpp"
#include "../grid/entity_iterator.hpp"
#include "../grid/entity.hpp"
#include "../grid/mapper.hpp"
#include "../grid/vtk_writer_helper.hpp"
#include "../space/space.hpp"
#include "identity_operator.hpp"
#include "../io/gmsh.hpp"

#include <boost/array.hpp>
#include <fstream>
#include <set>
#include <sstream>

namespace Bempp {

// Internal routines

namespace {

template <typename BasisFunctionType, typename ResultType>
shared_ptr<arma::Col<ResultType>> reallyCalculateProjections(
    const Space<BasisFunctionType> &dualSpace,
    Fiber::LocalAssemblerForGridFunctions<ResultType> &assembler,
    const AssemblyOptions &options) {
  // TODO: parallelise using TBB (the parameter options will then start be used)

  // Get the grid's leaf view so that we can iterate over elements
  const GridView &view = dualSpace.gridView();
  const size_t elementCount = view.entityCount(0);

  // Global DOF indices corresponding to local DOFs on elements
  std::vector<std::vector<GlobalDofIndex>> testGlobalDofs(elementCount);
  std::vector<std::vector<BasisFunctionType>> testLocalDofWeights(elementCount);

  // Gather global DOF lists
  const Mapper &mapper = view.elementMapper();
  std::unique_ptr<EntityIterator<0>> it = view.entityIterator<0>();
  while (!it->finished()) {
    const Entity<0> &element = it->entity();
    const int elementIndex = mapper.entityIndex(element);
    dualSpace.getGlobalDofs(element, testGlobalDofs[elementIndex],
                            testLocalDofWeights[elementIndex]);
    it->next();
  }

  // Make a vector of all element indices
  std::vector<int> testIndices(elementCount);
  for (size_t i = 0; i < elementCount; ++i)
    testIndices[i] = i;

  // Create the weak form's column vector
  shared_ptr<arma::Col<ResultType>> result(
      new arma::Col<ResultType>(dualSpace.globalDofCount()));
  result->fill(0.);

  std::vector<arma::Col<ResultType>> localResult;
  // Evaluate local weak forms
  assembler.evaluateLocalWeakForms(testIndices, localResult);

  // Loop over test indices
  for (size_t testIndex = 0; testIndex < elementCount; ++testIndex)
    // Add the integrals to appropriate entries in the global weak form
    for (size_t testDof = 0; testDof < testGlobalDofs[testIndex].size();
         ++testDof) {
      int testGlobalDof = testGlobalDofs[testIndex][testDof];
      if (testGlobalDof >= 0) // if it's negative, it means that this
                              // local dof is constrained (not used)
        (*result)(testGlobalDof) +=
            conj(testLocalDofWeights[testIndex][testDof]) *
            localResult[testIndex](testDof);
    }

  // Return the vector of projections <phi_i, f>
  return result;
}

/** \brief Calculate projections of the function on the basis functions of
  the given dual space. */
template <typename BasisFunctionType, typename ResultType>
shared_ptr<arma::Col<ResultType>>
calculateProjections(const Context<BasisFunctionType, ResultType> &context,
                     const Function<ResultType> &globalFunction,
                     const Space<BasisFunctionType> &dualSpace) {
  const AssemblyOptions &options = context.assemblyOptions();

  // Prepare local assembler
  typedef typename Fiber::ScalarTraits<ResultType>::RealType CoordinateType;
  typedef Fiber::RawGridGeometry<CoordinateType> RawGridGeometry;
  typedef std::vector<const Fiber::Shapeset<BasisFunctionType> *>
  ShapesetPtrVector;
  typedef LocalAssemblerConstructionHelper Helper;

  shared_ptr<RawGridGeometry> rawGeometry;
  shared_ptr<GeometryFactory> geometryFactory;
  shared_ptr<Fiber::OpenClHandler> openClHandler;
  shared_ptr<ShapesetPtrVector> testShapesets;

  Helper::collectGridData(dualSpace, rawGeometry, geometryFactory);
  Helper::makeOpenClHandler(options.parallelizationOptions().openClOptions(),
                            rawGeometry, openClHandler);
  Helper::collectShapesets(dualSpace, testShapesets);

  // Get reference to the test shapeset transformation
  const Fiber::CollectionOfShapesetTransformations<CoordinateType> &
  testTransformations = dualSpace.basisFunctionValue();

  typedef Fiber::LocalAssemblerForGridFunctions<ResultType> LocalAssembler;
  std::unique_ptr<LocalAssembler> assembler =
      context.quadStrategy()->makeAssemblerForGridFunctions(
          geometryFactory, rawGeometry, testShapesets,
          make_shared_from_ref(testTransformations),
          make_shared_from_ref(globalFunction), openClHandler);

  return reallyCalculateProjections(dualSpace, *assembler, options);
}

/** \brief Evaluate the function at the interpolation points of the chosen
 * space. */
template <typename BasisFunctionType, typename ResultType>
arma::Col<ResultType> interpolate(const Function<ResultType> &globalFunction,
                                  const Space<BasisFunctionType> &space) {
  size_t deps = 0;
  globalFunction.addGeometricalDependencies(deps);
  if (deps & ~(Fiber::GLOBALS | Fiber::NORMALS))
    throw std::invalid_argument(
        "interpolate(): functions to be interpolated "
        "must not depend on any geometrical data besides global "
        "coordinates and normal vectors");

  typedef typename Fiber::ScalarTraits<ResultType>::RealType CoordinateType;
  Fiber::GeometricalData<CoordinateType> geomData;
  if (deps & Fiber::GLOBALS) {
    space.getGlobalDofInterpolationPoints(geomData.globals);
  }
  if (deps & Fiber::NORMALS) {
    space.getNormalsAtGlobalDofInterpolationPoints(geomData.normals);
  }
  arma::Mat<ResultType> values;
  globalFunction.evaluate(geomData, values);

  const size_t componentCount = values.n_rows;
  const size_t pointCount = values.n_cols;

  arma::Mat<CoordinateType> directions;
  space.getGlobalDofInterpolationDirections(directions);
  assert(directions.n_rows == values.n_rows);
  assert(directions.n_cols == pointCount);

  arma::Col<ResultType> result(pointCount);
  result.fill(0);
  for (size_t p = 0; p < pointCount; ++p)
    for (size_t d = 0; d < componentCount; ++d)
      result(p) += values(d, p) * directions(d, p);
  return result;
}

} // namespace

// Recommended constructors

template <typename BasisFunctionType, typename ResultType>
GridFunction<BasisFunctionType, ResultType>::GridFunction() {}

template <typename BasisFunctionType, typename ResultType>
GridFunction<BasisFunctionType, ResultType>::GridFunction(
    const shared_ptr<const Context<BasisFunctionType, ResultType>> &context,
    const shared_ptr<const Space<BasisFunctionType>> &space,
    const arma::Col<ResultType> &coefficients) {
  initializeFromCoefficients(context, space, coefficients);
}

template <typename BasisFunctionType, typename ResultType>
GridFunction<BasisFunctionType, ResultType>::GridFunction(
    const shared_ptr<const Context<BasisFunctionType, ResultType>> &context,
    const shared_ptr<const Space<BasisFunctionType>> &space,
    const shared_ptr<const Space<BasisFunctionType>> &dualSpace,
    const arma::Col<ResultType> &projections) {

  initializeFromProjections(context, space, dualSpace, projections);
  m_dualSpace = dualSpace;
}

template <typename BasisFunctionType, typename ResultType>
GridFunction<BasisFunctionType, ResultType>::GridFunction(
    const shared_ptr<const Context<BasisFunctionType, ResultType>> &context,
    const shared_ptr<const Space<BasisFunctionType>> &space,
    const shared_ptr<const Space<BasisFunctionType>> &dualSpace,
    const Function<ResultType> &function, ConstructionMode mode)
    : m_context(context), m_space(space), m_dualSpace(dualSpace) {
  if (!context)
    throw std::invalid_argument(
        "GridFunction::GridFunction(): context must not be null");
  if (!space)
    throw std::invalid_argument(
        "GridFunction::GridFunction(): space must not be null");
  if (!dualSpace)
    throw std::invalid_argument(
        "GridFunction::GridFunction(): dualSpace must not be null");

  if (space->codomainDimension() != dualSpace->codomainDimension())
    throw std::invalid_argument(
        "GridFunction::GridFunction(): "
        "functions from 'space' and 'dualSpace' have a different "
        "number of components");
  if (function.codomainDimension() != space->codomainDimension())
    throw std::invalid_argument(
        "GridFunction::GridFunction(): "
        "functions from 'space' have a different number of "
        "components than 'function'");
  if (mode != APPROXIMATE && mode != INTERPOLATE)
    throw std::invalid_argument(
        "GridFunction::GridFunction(): "
        "'mode' must be either APPROXIMATE or INTERPOLATE");

  bool isBarycentricSpace =
      (space->isBarycentric() || dualSpace->isBarycentric());
  if (isBarycentricSpace) {
    m_space = space->barycentricSpace(space);
    m_dualSpace = dualSpace->barycentricSpace(dualSpace);
  }
  if (m_space->grid() != m_dualSpace->grid())
    throw std::invalid_argument(
        "GridFunction::GridFunction(): "
        "space and dualSpace must be defined on the same grid");

  if (mode == APPROXIMATE)
    setProjections(m_dualSpace,
                   *calculateProjections(*context, function, *m_dualSpace));
  else // mode == INTERPOLATE
    setCoefficients(interpolate(function, *m_space));
}

// Deprecated constructors

template <typename BasisFunctionType, typename ResultType>
GridFunction<BasisFunctionType, ResultType>::GridFunction(
    const shared_ptr<const Context<BasisFunctionType, ResultType>> &context,
    const shared_ptr<const Space<BasisFunctionType>> &space,
    const shared_ptr<const Space<BasisFunctionType>> &dualSpace,
    const arma::Col<ResultType> &data, DataType dataType) {
  bool isBarycentricSpace = false;
  if (space && space->isBarycentric())
    isBarycentricSpace = true;

  if (dualSpace && dualSpace->isBarycentric())
    isBarycentricSpace = true;

  shared_ptr<const Space<BasisFunctionType>> newSpace(space);
  shared_ptr<const Space<BasisFunctionType>> newDualSpace(dualSpace);
  if (isBarycentricSpace) {
    newSpace = space->barycentricSpace(space);
    if (dualSpace)
      newDualSpace = dualSpace->barycentricSpace(dualSpace);
  }

  if (dataType == COEFFICIENTS) {
    if (newDualSpace && newSpace->grid() != newDualSpace->grid())
      throw std::invalid_argument(
          "GridFunction::GridFunction(): "
          "space and dualSpace must be defined on the same grid");
    initializeFromCoefficients(context, newSpace, data);
    m_dualSpace = newDualSpace;
  } else if (dataType == PROJECTIONS) {
    initializeFromProjections(context, newSpace, newDualSpace, data);
    m_dualSpace = newDualSpace;
  } else
    throw std::invalid_argument(
        "GridFunction::GridFunction(): invalid dataType");
}

template <typename BasisFunctionType, typename ResultType>
GridFunction<BasisFunctionType, ResultType>::GridFunction(
    const shared_ptr<const Context<BasisFunctionType, ResultType>> &context,
    const shared_ptr<const Space<BasisFunctionType>> &space,
    const shared_ptr<const Space<BasisFunctionType>> &dualSpace,
    const arma::Col<ResultType> &coefficients,
    const arma::Col<ResultType> &projections) {
  bool isBarycentricSpace = false;
  if (space && space->isBarycentric())
    isBarycentricSpace = true;

  if (dualSpace && dualSpace->isBarycentric())
    isBarycentricSpace = true;

  shared_ptr<const Space<BasisFunctionType>> newSpace(space);
  shared_ptr<const Space<BasisFunctionType>> newDualSpace(dualSpace);
  if (isBarycentricSpace) {
    newSpace = space->barycentricSpace(space);
    if (dualSpace)
      newDualSpace = dualSpace->barycentricSpace(dualSpace);
  }

  // We ignore the vector of projections.
  initializeFromCoefficients(context, newSpace, coefficients);
  if (newDualSpace && newSpace->grid() != newDualSpace->grid())
    throw std::invalid_argument(
        "GridFunction::GridFunction(): "
        "space and dualSpace must be defined on the same grid");
  m_dualSpace = newDualSpace;
}

// Member functions

template <typename BasisFunctionType, typename ResultType>
void GridFunction<BasisFunctionType, ResultType>::initializeFromCoefficients(
    const shared_ptr<const Context<BasisFunctionType, ResultType>> &context,
    const shared_ptr<const Space<BasisFunctionType>> &space,
    const arma::Col<ResultType> &coefficients) {
  if (!context)
    throw std::invalid_argument(
        "GridFunction::initializeFromCoefficients(): context must not be null");
  if (!space)
    throw std::invalid_argument(
        "GridFunction::initializeFromCoefficients(): space must not be null");
  if (coefficients.n_rows != space->globalDofCount())
    throw std::invalid_argument("GridFunction::initializeFromCoefficients(): "
                                "the coefficients vector has incorrect length");
  m_context = context;
  m_space = space;
  setCoefficients(coefficients);
}

template <typename BasisFunctionType, typename ResultType>
void GridFunction<BasisFunctionType, ResultType>::initializeFromProjections(
    const shared_ptr<const Context<BasisFunctionType, ResultType>> &context,
    const shared_ptr<const Space<BasisFunctionType>> &space,
    const shared_ptr<const Space<BasisFunctionType>> &dualSpace,
    const arma::Col<ResultType> &projections) {
  if (!context)
    throw std::invalid_argument(
        "GridFunction::initializeFromProjections(): context must not be null");
  if (!space)
    throw std::invalid_argument(
        "GridFunction::initializeFromProjections(): space must not be null");
  if (!dualSpace)
    throw std::invalid_argument("GridFunction::initializeFromProjections(): "
                                "dualSpace must not be null");
  bool isBarycentricSpace =
      (space->isBarycentric() || dualSpace->isBarycentric());
  if (isBarycentricSpace) {
    m_space = space->barycentricSpace(space);
    m_dualSpace = dualSpace->barycentricSpace(dualSpace);
  } else {
    m_space = space;
    m_dualSpace = dualSpace;
  }
  if (m_space->grid() != m_dualSpace->grid())
    throw std::invalid_argument(
        "GridFunction::initializeFromProjections(): "
        "space and dualSpace must be defined on the same grid");

  if (projections.n_rows != dualSpace->globalDofCount())
    throw std::invalid_argument("GridFunction::initializeFromProjections(): "
                                "the projections vector has incorrect length");
  m_context = context;
  setProjections(m_dualSpace, projections);
}

template <typename BasisFunctionType, typename ResultType>
bool GridFunction<BasisFunctionType, ResultType>::isInitialized() const {
  return (bool)m_space;
}

template <typename BasisFunctionType, typename ResultType>
bool
GridFunction<BasisFunctionType, ResultType>::wasInitializedFromCoefficients()
    const {
  return m_wasInitializedFromCoefficients;
}

template <typename BasisFunctionType, typename ResultType>
shared_ptr<const Grid>
GridFunction<BasisFunctionType, ResultType>::grid() const {
  if (!m_space)
    throw std::runtime_error("GridFunction::grid() must not be called "
                             "on an uninitialized GridFunction object");

  return m_space->grid();
}

template <typename BasisFunctionType, typename ResultType>
shared_ptr<const Space<BasisFunctionType>>
GridFunction<BasisFunctionType, ResultType>::space() const {
  return m_space;
}

template <typename BasisFunctionType, typename ResultType>
shared_ptr<const Space<BasisFunctionType>>
GridFunction<BasisFunctionType, ResultType>::dualSpace() const {
  return m_dualSpace;
}

template <typename BasisFunctionType, typename ResultType>
shared_ptr<const Context<BasisFunctionType, ResultType>>
GridFunction<BasisFunctionType, ResultType>::context() const {
  return m_context;
}

template <typename BasisFunctionType, typename ResultType>
int GridFunction<BasisFunctionType, ResultType>::componentCount() const {
  if (!m_space)
    throw std::runtime_error(
        "GridFunction::componentCount() must not be called "
        "on an uninitialized GridFunction object");

  return m_space->codomainDimension();
}

template <typename BasisFunctionType, typename ResultType>
GridFunction<BasisFunctionType, ResultType>
GridFunction<BasisFunctionType, ResultType>::approximateInSpace(
    const shared_ptr<Space<BasisFunctionType>> &space) const {

  BoundaryOperator<BasisFunctionType, ResultType> id =
      identityOperator<BasisFunctionType, ResultType>(m_context, m_space, space,
                                                      space);
  return id * (*this);
}

template <typename BasisFunctionType, typename ResultType>
const arma::Col<ResultType> &
GridFunction<BasisFunctionType, ResultType>::coefficients() const {
  if (!m_space || (!m_coefficients && !m_projections))
    throw std::runtime_error("GridFunction::coefficients() must not be called "
                             "on an uninitialized GridFunction object");
  if (!m_coefficients)
    updateCoefficientsFromProjections();
  return *m_coefficients;
}

template <typename BasisFunctionType, typename ResultType>
void GridFunction<BasisFunctionType, ResultType>::setCoefficients(
    const arma::Col<ResultType> &coeffs) {
  if (!m_space)
    throw std::runtime_error(
        "GridFunction::setCoefficients() must not be called "
        "on an uninitialized GridFunction object");
  if (coeffs.n_rows != m_space->globalDofCount())
    throw std::invalid_argument(
        "GridFunction::setCoefficients(): dimension of the provided "
        "vector does not match the number of global DOFs in the primal space");
  m_coefficients.reset(new arma::Col<ResultType>(coeffs));
  m_projections.reset();
  m_wasInitializedFromCoefficients = true;
}

template <typename BasisFunctionType, typename ResultType>
arma::Col<ResultType> GridFunction<BasisFunctionType, ResultType>::projections(
    const shared_ptr<const Space<BasisFunctionType>> &dualSpace_) const {
  if (!m_space)
    throw std::runtime_error("GridFunction::projections() must not be called "
                             "on an uninitialized GridFunction object");
  if (m_space->grid() != dualSpace_->grid())
    if (!m_space->grid()->isBarycentricRepresentationOf(*dualSpace_->grid()) &&
        !dualSpace_->grid()->isBarycentricRepresentationOf(*m_space->grid()))
      throw std::invalid_argument(
          "GridFunction::projections(): "
          "space and dual space must be defined on the same grid");

  if (!m_coefficients && !m_projections)
    throw std::runtime_error("GridFunction::projections() must not be called "
                             "on an uninitialized GridFunction object");
  if (!m_projections || !m_dualSpace->spaceIsCompatible(*dualSpace_))
    updateProjectionsFromCoefficients(dualSpace_);
  return *m_projections;
}

// Deprecated version
template <typename BasisFunctionType, typename ResultType>
arma::Col<ResultType> GridFunction<BasisFunctionType, ResultType>::projections(
    const Space<BasisFunctionType> &dualSpace_) const {
  if (!m_dualSpace)
    throw std::runtime_error(
        "You must provide the dualSpace_ argument in the call to "
        "GridFunction::projections() if you did not specify the "
        "dual space when constructing the GridFunction.");
  arma::Col<ResultType> result = projections(make_shared_from_ref(dualSpace_));
  // update the coefficients because we can't guarantee
  // dualSpace_ will stay alive until they are needed
  coefficients();
  return result;
}

// Deprecated version
template <typename BasisFunctionType, typename ResultType>
arma::Col<ResultType>
GridFunction<BasisFunctionType, ResultType>::projections() const {
  if (!m_dualSpace)
    throw std::runtime_error(
        "You must provide the dualSpace_ argument in the call to "
        "GridFunction::projections() if you did not specify the "
        "dual space when constructing the GridFunction.");
  return projections(m_dualSpace);
}

template <typename BasisFunctionType, typename ResultType>
void GridFunction<BasisFunctionType, ResultType>::setProjections(
    const shared_ptr<const Space<BasisFunctionType>> &dualSpace_,
    const arma::Col<ResultType> &projects) {
  if (!m_space)
    throw std::runtime_error(
        "GridFunction::setProjections() must not be called "
        "on an uninitialized GridFunction object");
  if (m_space->grid() != dualSpace_->grid())
    throw std::invalid_argument(
        "GridFunction::setProjections(): "
        "space and dual space must be defined on the same grid");
  if (projects.n_rows != dualSpace_->globalDofCount())
    throw std::invalid_argument(
        "GridFunction::setProjections(): dimension of the provided "
        "vector does not match the number of global DOFs in the dual space");

  m_projections = boost::make_shared<arma::Col<ResultType>>(projects);
  m_dualSpace = dualSpace_;
  m_coefficients.reset();
  m_wasInitializedFromCoefficients = false;
}

// Deprecated version
template <typename BasisFunctionType, typename ResultType>
void GridFunction<BasisFunctionType, ResultType>::setProjections(
    const Space<BasisFunctionType> &dualSpace_,
    const arma::Col<ResultType> &projects) {
  setProjections(make_shared_from_ref(dualSpace_), projects);
  // update the coefficients because we can't guarantee
  // dualSpace_ will stay alive until they are needed
  coefficients();
}

// Deprecated version
template <typename BasisFunctionType, typename ResultType>
void GridFunction<BasisFunctionType, ResultType>::setProjections(
    const arma::Col<ResultType> &projects) {
  if (!m_dualSpace)
    throw std::runtime_error(
        "You must provide the dualSpace_ argument in the call to "
        "GridFunction::setProjections() if you did not specify the "
        "dual space when constructing the GridFunction.");
  setProjections(m_dualSpace, projects);
}

template <typename BasisFunctionType, typename ResultType>
void
GridFunction<BasisFunctionType, ResultType>::updateProjectionsFromCoefficients(
    const shared_ptr<const Space<BasisFunctionType>> &dualSpace_) const {
  // This should have been checked beforehand, this is after all a private
  // function. So omit these checks in release mode.
  assert(isInitialized());
  assert(dualSpace_);
  assert(m_coefficients);

  // Calculate the mass matrix
  typedef BoundaryOperator<BasisFunctionType, ResultType> BoundaryOp;
  BoundaryOp id = identityOperator(m_context, m_space, m_space, dualSpace_);

  shared_ptr<arma::Col<ResultType>> newProjections(
      new arma::Col<ResultType>(dualSpace_->globalDofCount()));
  id.weakForm()->apply(NO_TRANSPOSE, *m_coefficients, *newProjections,
                       static_cast<ResultType>(1.),
                       static_cast<ResultType>(0.));
  m_projections = newProjections;
  m_dualSpace = dualSpace_;
}

template <typename BasisFunctionType, typename ResultType>
void
GridFunction<BasisFunctionType, ResultType>::updateCoefficientsFromProjections()
    const {
  // This should have been checked beforehand, this is after all a private
  // function. So omit these checks in release mode.
  assert(isInitialized());
  assert(m_projections);
  assert(m_dualSpace);

  // Calculate the (pseudo)inverse mass matrix
  typedef BoundaryOperator<BasisFunctionType, ResultType> BoundaryOp;
  BoundaryOp id = identityOperator(m_context, m_space, m_space, m_dualSpace);
  BoundaryOp pinvId = pseudoinverse(id);

  shared_ptr<arma::Col<ResultType>> newCoefficients(
      new arma::Col<ResultType>(m_space->globalDofCount()));
  pinvId.weakForm()->apply(NO_TRANSPOSE, *m_projections, *newCoefficients,
                           static_cast<ResultType>(1.),
                           static_cast<ResultType>(0.));
  m_coefficients = newCoefficients;
}

template <typename BasisFunctionType, typename ResultType>
typename GridFunction<BasisFunctionType, ResultType>::MagnitudeType
GridFunction<BasisFunctionType, ResultType>::L2Norm() const {
  // The L^2 norm is given by
  //   sqrt(u^\dagger M u),
  // where u is the coefficient vector and M the mass matrix of m_space

  if (!m_space)
    throw std::runtime_error("GridFunction::L2_Norm() must not be called "
                             "on an uninitialized GridFunction object");
  typedef BoundaryOperator<BasisFunctionType, ResultType> BoundaryOp;

  // Get the vector of coefficients
  const arma::Col<ResultType> &coeffs = coefficients();

  // Calculate the mass matrix
  BoundaryOp id = identityOperator(m_context, m_space, m_space, m_space);
  shared_ptr<const DiscreteBoundaryOperator<ResultType>> massMatrix =
      id.weakForm();

  arma::Col<ResultType> product(coeffs.n_rows);
  massMatrix->apply(NO_TRANSPOSE, coeffs, product, 1., 0.);
  ResultType result = arma::cdot(coeffs, product);
  if (fabs(imagPart(result)) >
      1000. * std::numeric_limits<MagnitudeType>::epsilon())
    std::cout << "Warning: squared L2Norm has non-negligible imaginary part: "
              << imagPart(result) << std::endl;
  return sqrt(realPart(result));
}

// Redundant, in fact -- can be obtained directly from Space
template <typename BasisFunctionType, typename ResultType>
const Fiber::Shapeset<BasisFunctionType> &
GridFunction<BasisFunctionType, ResultType>::shapeset(const Entity<0> &element)
    const {
  BOOST_ASSERT_MSG(m_space, "GridFunction::shapeset() must not be "
                            "called on an uninitialized GridFunction object");
  return m_space->shapeset(element);
}

template <typename BasisFunctionType, typename ResultType>
void GridFunction<BasisFunctionType, ResultType>::getLocalCoefficients(
    const Entity<0> &element, std::vector<ResultType> &coeffs) const {
  BOOST_ASSERT_MSG(m_space, "GridFunction::getLocalCoefficients() must not be "
                            "called on an uninitialized GridFunction object");
  std::vector<GlobalDofIndex> gdofIndices;
  std::vector<BasisFunctionType> ldofWeights;
  m_space->getGlobalDofs(element, gdofIndices, ldofWeights);
  const int gdofCount = gdofIndices.size();
  coeffs.resize(gdofCount);
  const arma::Col<ResultType> &globalCoefficients = coefficients();
  for (int i = 0; i < gdofCount; ++i) {
    int gdof = gdofIndices[i];
    if (gdof >= 0)
      coeffs[i] = globalCoefficients(gdof) * ldofWeights[i];
    else
      coeffs[i] = 0.;
  }
}

template <typename BasisFunctionType, typename ResultType>
void GridFunction<BasisFunctionType, ResultType>::exportToVtk(
    VtkWriter::DataType dataType, const char *dataLabel,
    const char *fileNamesBase, const char *filesPath,
    VtkWriter::OutputType outputType) const {
  Bempp::exportToVtk(*this, dataType, dataLabel, fileNamesBase, filesPath,
                     outputType);
}

template <typename BasisFunctionType, typename ResultType>
void GridFunction<BasisFunctionType, ResultType>::evaluateAtSpecialPoints(
    VtkWriter::DataType dataType, arma::Mat<ResultType> &result_) const {
  arma::Mat<CoordinateType> points;
  evaluateAtSpecialPoints(dataType, points, result_);
}

template <typename BasisFunctionType, typename ResultType>
void GridFunction<BasisFunctionType, ResultType>::evaluateAtSpecialPoints(
    VtkWriter::DataType dataType, arma::Mat<CoordinateType> &points,
    arma::Mat<ResultType> &values) const {
  if (!m_space)
    throw std::runtime_error(
        "GridFunction::evaluateAtSpecialPoints() must "
        "not be called on an uninitialized GridFunction object");
  if (dataType != VtkWriter::CELL_DATA && dataType != VtkWriter::VERTEX_DATA)
    throw std::invalid_argument("GridFunction::evaluateAtSpecialPoints(): "
                                "invalid data type");

  const GridView &view = m_space->gridView();
  const int gridDim = m_space->gridDimension();
  const int worldDim = m_space->worldDimension();
  const int elementCodim = 0;
  const int vertexCodim = gridDim;
  const int nComponents = componentCount();

  const size_t elementCount = view.entityCount(elementCodim);
  const size_t vertexCount = view.entityCount(vertexCodim);

  values.set_size(nComponents, dataType == VtkWriter::CELL_DATA ? elementCount
                                                                : vertexCount);
  values.fill(0.);
  points.set_size(worldDim, values.n_cols);

  // Number of elements contributing to each column in result
  // (this will be greater than 1 for VERTEX_DATA)
  std::vector<int> multiplicities(vertexCount);
  std::fill(multiplicities.begin(), multiplicities.end(), 0);

  // Gather geometric data
  Fiber::RawGridGeometry<CoordinateType> rawGeometry(gridDim,
                                                     m_space->worldDimension());
  view.getRawElementData(rawGeometry.vertices(),
                         rawGeometry.elementCornerIndices(),
                         rawGeometry.auxData(), rawGeometry.domainIndices());

  // Make geometry factory
  shared_ptr<const Grid> grid = m_space->grid();
  std::unique_ptr<GeometryFactory> geometryFactory =
      grid->elementGeometryFactory();
  std::unique_ptr<typename GeometryFactory::Geometry> geometry(
      geometryFactory->make());
  Fiber::GeometricalData<CoordinateType> geomData;

  // For each element, get its shapeset and corner count (this is sufficient
  // to identify its geometry) as well as its local coefficients
  typedef std::pair<const Fiber::Shapeset<BasisFunctionType> *, int>
  ShapesetAndCornerCount;
  typedef std::vector<ShapesetAndCornerCount> ShapesetAndCornerCountVector;
  ShapesetAndCornerCountVector basesAndCornerCounts(elementCount);
  std::vector<std::vector<ResultType>> localCoefficients(elementCount);
  {
    const Mapper &mapper = view.elementMapper();
    std::unique_ptr<EntityIterator<0>> it = view.entityIterator<0>();
    for (size_t e = 0; e < elementCount; ++e) {
      const Entity<0> &element = it->entity();
      const int elementIndex = mapper.entityIndex(element);
      basesAndCornerCounts[elementIndex] =
          ShapesetAndCornerCount(&m_space->shapeset(element),
                                 rawGeometry.elementCornerCount(elementIndex));
      getLocalCoefficients(element, localCoefficients[elementIndex]);
      it->next();
    }
  }

  typedef std::set<ShapesetAndCornerCount> ShapesetAndCornerCountSet;
  ShapesetAndCornerCountSet uniqueShapesetsAndCornerCounts(
      basesAndCornerCounts.begin(), basesAndCornerCounts.end());

  // Find out which basis data need to be calculated
  size_t basisDeps = 0, geomDeps = Fiber::GLOBALS;
  // Find out which geometrical data need to be calculated, in addition
  // to those needed by the kernel
  const Fiber::CollectionOfShapesetTransformations<CoordinateType> &
  transformations = m_space->basisFunctionValue();
  assert(nComponents == transformations.resultDimension(0));
  transformations.addDependencies(basisDeps, geomDeps);

  // Loop over unique combinations of basis and element corner count
  typedef typename ShapesetAndCornerCountSet::const_iterator
  BasisAndCornerCountSetConstIt;
  for (BasisAndCornerCountSetConstIt it =
           uniqueShapesetsAndCornerCounts.begin();
       it != uniqueShapesetsAndCornerCounts.end(); ++it) {
    const ShapesetAndCornerCount &activeBasisAndCornerCount = *it;
    const Fiber::Shapeset<BasisFunctionType> &activeShapeset =
        *activeBasisAndCornerCount.first;
    int activeCornerCount = activeBasisAndCornerCount.second;

    // Set the local coordinates of either all vertices or the barycentre
    // of the active element type
    arma::Mat<CoordinateType> local;
    if (dataType == VtkWriter::CELL_DATA) {
      local.set_size(gridDim, 1);

      // We could actually use Dune for these assignements
      if (gridDim == 1 && activeCornerCount == 2) {
        // linear segment
        local(0, 0) = 0.5;
      } else if (gridDim == 2 && activeCornerCount == 3) {
        // triangle
        local(0, 0) = 1. / 3.;
        local(1, 0) = 1. / 3.;
      } else if (gridDim == 2 && activeCornerCount == 4) {
        // quadrilateral
        local(0, 0) = 0.5;
        local(1, 0) = 0.5;
      } else
        throw std::runtime_error("GridFunction::evaluateAtVertices(): "
                                 "unsupported element type");
    } else { // VERTEX_DATA
      local.set_size(gridDim, activeCornerCount);

      // We could actually use Dune for these assignements
      if (gridDim == 1 && activeCornerCount == 2) {
        // linear segment
        local(0, 0) = 0.;
        local(0, 1) = 1.;
      } else if (gridDim == 2 && activeCornerCount == 3) {
        // triangle
        local.fill(0.);
        local(0, 1) = 1.;
        local(1, 2) = 1.;
      } else if (gridDim == 2 && activeCornerCount == 4) {
        // quadrilateral
        local.fill(0.);
        local(0, 1) = 1.;
        local(1, 2) = 1.;
        local(0, 3) = 1.;
        local(1, 3) = 1.;
      } else
        throw std::runtime_error("GridFunction::evaluateAtVertices(): "
                                 "unsupported element type");
    }

    // Get basis data
    Fiber::BasisData<BasisFunctionType> basisData;
    activeShapeset.evaluate(basisDeps, local, ALL_DOFS, basisData);

    Fiber::BasisData<ResultType> functionData;
    if (basisDeps & Fiber::VALUES)
      functionData.values.set_size(basisData.values.extent(0),
                                   1, // just one function
                                   basisData.values.extent(2));
    if (basisDeps & Fiber::DERIVATIVES)
      functionData.derivatives.set_size(basisData.derivatives.extent(0),
                                        basisData.derivatives.extent(1),
                                        1, // just one function
                                        basisData.derivatives.extent(3));
    Fiber::CollectionOf3dArrays<ResultType> functionValues;

    // Loop over elements and process those that use the active shapeset
    for (size_t e = 0; e < elementCount; ++e) {
      if (basesAndCornerCounts[e].first != &activeShapeset)
        continue;

      // Local coefficients of the argument in the current element
      const std::vector<ResultType> &activeLocalCoefficients =
          localCoefficients[e];

      // Calculate the function's values and/or derivatives
      // at the requested points in the current element
      if (basisDeps & Fiber::VALUES) {
        std::fill(functionData.values.begin(), functionData.values.end(), 0.);
        for (size_t point = 0; point < basisData.values.extent(2); ++point)
          for (size_t dim = 0; dim < basisData.values.extent(0); ++dim)
            for (size_t fun = 0; fun < basisData.values.extent(1); ++fun)
              functionData.values(dim, 0, point) +=
                  basisData.values(dim, fun, point) *
                  activeLocalCoefficients[fun];
      }
      if (basisDeps & Fiber::DERIVATIVES) {
        std::fill(functionData.derivatives.begin(),
                  functionData.derivatives.end(), 0.);
        for (size_t point = 0; point < basisData.derivatives.extent(3); ++point)
          for (size_t dim = 0; dim < basisData.derivatives.extent(1); ++dim)
            for (size_t comp = 0; comp < basisData.derivatives.extent(0);
                 ++comp)
              for (size_t fun = 0; fun < basisData.derivatives.extent(2); ++fun)
                functionData.derivatives(comp, dim, 0, point) +=
                    basisData.derivatives(comp, dim, fun, point) *
                    activeLocalCoefficients[fun];
      }

      // Get geometrical data
      rawGeometry.setupGeometry(e, *geometry);
      geometry->getData(geomDeps, local, geomData);
      if (geomDeps & Fiber::DOMAIN_INDEX)
        geomData.domainIndex = rawGeometry.domainIndex(e);

      transformations.evaluate(functionData, geomData, functionValues);
      assert(functionValues[0].extent(1) == 1); // one function

      if (dataType == VtkWriter::CELL_DATA) {
        for (int dim = 0; dim < nComponents; ++dim)
          values(dim, e) = functionValues[0]( // array index
              dim,                            // component
              0,                              // function index
              0);                             // point index
        for (int dim = 0; dim < worldDim; ++dim)
          points(dim, e) = geomData.globals(dim, 0);
      } else { // VERTEX_DATA
        // Add the calculated values to the columns of the result array
        // corresponding to the active element's vertices
        for (int c = 0; c < activeCornerCount; ++c) {
          int vertexIndex = rawGeometry.elementCornerIndices()(c, e);
          for (int dim = 0; dim < nComponents; ++dim)
            values(dim, vertexIndex) += functionValues[0](dim, 0, c);
          ++multiplicities[vertexIndex];
        }
        for (int c = 0; c < activeCornerCount; ++c) {
          int vertexIndex = rawGeometry.elementCornerIndices()(c, e);
          for (int dim = 0; dim < worldDim; ++dim)
            points(dim, vertexIndex) = geomData.globals(dim, c);
        }
      }
    } // end of loop over elements
  }   // end of loop over unique combinations of shapeset and corner count

  // Take average of the vertex values obtained in each of the adjacent elements
  if (dataType == VtkWriter::VERTEX_DATA)
    for (size_t v = 0; v < vertexCount; ++v)
      values.col(v) /= multiplicities[v];
}

template <typename BasisFunctionType, typename ResultType>
void GridFunction<BasisFunctionType, ResultType>::evaluate(
    const Entity<0> &element, const arma::Mat<CoordinateType> &local,
    arma::Mat<ResultType> &values) const {
  if (local.n_rows != m_space->grid()->dim())
    throw std::invalid_argument("evaluate(): points in 'local' have an "
                                "invalid number of coordinates");

  const int nComponents = componentCount();
  // Find out which basis data need to be calculated
  size_t basisDeps = 0, geomDeps = 0;
  // Find out which geometrical data need to be calculated,
  const Fiber::CollectionOfShapesetTransformations<CoordinateType> &
  transformations = m_space->basisFunctionValue();
  assert(transformations.transformationCount() == 1);
  assert(nComponents == transformations.resultDimension(0));
  transformations.addDependencies(basisDeps, geomDeps);

  // Get basis data
  const Fiber::Shapeset<BasisFunctionType> &shapeset =
      m_space->shapeset(element);
  Fiber::BasisData<BasisFunctionType> basisData;
  shapeset.evaluate(basisDeps, local, ALL_DOFS, basisData);
  // Get geometrical data
  Fiber::GeometricalData<CoordinateType> geomData;
  element.geometry().getData(geomDeps, local, geomData);
  values.set_size(nComponents, local.n_cols);
  // Get shape function values
  Fiber::CollectionOf3dArrays<BasisFunctionType> functionValues;
  transformations.evaluate(basisData, geomData, functionValues);
  assert(functionValues.size() == 1);

  // Get local coefficients
  std::vector<ResultType> localCoefficients(shapeset.size());
  getLocalCoefficients(element, localCoefficients);
  assert(localCoefficients.size() == shapeset.size());

  // Calculate grid function values
  values.set_size(functionValues[0].extent(0), local.n_cols);
  values.fill(static_cast<ResultType>(0.));
  for (size_t p = 0; p < functionValues[0].extent(2); ++p)
    for (size_t f = 0; f < functionValues[0].extent(1); ++f)
      for (size_t dim = 0; dim < functionValues[0].extent(0); ++dim)
        values(dim, p) += functionValues[0](dim, f, p) * localCoefficients[f];
}

template <typename BasisFunctionType, typename ResultType>
void GridFunction<BasisFunctionType, ResultType>::exportToGmsh(
    const char *dataLabel, const char *fileName) const {
  Bempp::exportToGmsh(*this, dataLabel, fileName);
}

template <typename BasisFunctionType, typename ResultType>
GridFunction<BasisFunctionType, ResultType>
operator+(const GridFunction<BasisFunctionType, ResultType> &g) {
  return g;
}

template <typename BasisFunctionType, typename ResultType>
GridFunction<BasisFunctionType, ResultType>
operator-(const GridFunction<BasisFunctionType, ResultType> &g) {
  return static_cast<ResultType>(-1.) * g;
}

template <typename BasisFunctionType, typename ResultType, typename ScalarType>
typename boost::enable_if<
    typename boost::mpl::has_key<
        boost::mpl::set<float, double, std::complex<float>,
                        std::complex<double>>,
        ScalarType>,
    GridFunction<BasisFunctionType, ResultType>>::type
operator*(const ScalarType &scalar,
          const GridFunction<BasisFunctionType, ResultType> &g2) {
  return g2 * scalar;
}

template <typename BasisFunctionType, typename ResultType, typename ScalarType>
GridFunction<BasisFunctionType, ResultType>
operator/(const GridFunction<BasisFunctionType, ResultType> &g1,
          const ScalarType &scalar) {
  if (scalar == static_cast<ScalarType>(0.))
    throw std::runtime_error("GridFunction::operator/(): Divide by zero");
  return (static_cast<ScalarType>(1.) / scalar) * g1;
}

template <typename BasisFunctionType, typename ResultType>
void
exportToVtk(const GridFunction<BasisFunctionType, ResultType> &gridFunction,
            VtkWriter::DataType dataType, const char *dataLabel,
            const char *fileNamesBase, const char *filesPath,
            VtkWriter::OutputType outputType) {
  shared_ptr<const Space<BasisFunctionType>> space = gridFunction.space();
  if (!space)
    throw std::runtime_error("exportToVtk(): gridFunction must not be "
                             "an uninitialized GridFunction object");
  arma::Mat<ResultType> data;
  gridFunction.evaluateAtSpecialPoints(dataType, data);

  std::unique_ptr<GridView> view = space->grid()->leafView();
  std::unique_ptr<VtkWriter> vtkWriter = view->vtkWriter();

  exportSingleDataSetToVtk(*vtkWriter, data, dataType, dataLabel, fileNamesBase,
                           filesPath, outputType);
}

BEMPP_GCC_DIAG_OFF(deprecated - declarations);

// Redundant, in fact -- can be obtained directly from Space
template <typename BasisFunctionType, typename ResultType>
const Fiber::Basis<BasisFunctionType> &
GridFunction<BasisFunctionType, ResultType>::basis(const Entity<0> &element)
    const {
  BOOST_ASSERT_MSG(m_space, "GridFunction::basis() must not be "
                            "called on an uninitialized GridFunction object");
  return m_space->basis(element);
}

template <typename BasisFunctionType, typename ResultType>
GridFunction<BasisFunctionType, ResultType>
operator+(const GridFunction<BasisFunctionType, ResultType> &g1,
          const GridFunction<BasisFunctionType, ResultType> &g2) {
  if (g1.space() != g2.space())
    throw std::runtime_error("GridFunction::operator+(): spaces don't match");
  if (g1.wasInitializedFromCoefficients() ||
      g2.wasInitializedFromCoefficients() || g1.dualSpace() != g2.dualSpace())
    return GridFunction<BasisFunctionType, ResultType>(
        g1.context(), // arbitrary choice...
        g1.space(), g1.coefficients() + g2.coefficients());
  else {
    shared_ptr<const Space<BasisFunctionType>> dualSpace = g1.dualSpace();
    return GridFunction<BasisFunctionType, ResultType>(
        g1.context(), // arbitrary choice...
        g1.space(), dualSpace,
        g1.projections(dualSpace) + g2.projections(dualSpace));
  }
}

template <typename BasisFunctionType, typename ResultType>
GridFunction<BasisFunctionType, ResultType>
operator-(const GridFunction<BasisFunctionType, ResultType> &g1,
          const GridFunction<BasisFunctionType, ResultType> &g2) {
  if (g1.space() != g2.space())
    throw std::runtime_error("GridFunction::operator-(): spaces don't match");
  // For the sake of old-style code (with dualSpace stored in the GridFunction),
  // we try to provide a sensible dual space to the composite GridFunction.
  if (g1.wasInitializedFromCoefficients() ||
      g2.wasInitializedFromCoefficients() || g1.dualSpace() != g2.dualSpace())
    return GridFunction<BasisFunctionType, ResultType>(
        g1.context(), // arbitrary choice...
        g1.space(), g1.coefficients() - g2.coefficients());
  else {
    shared_ptr<const Space<BasisFunctionType>> dualSpace = g1.dualSpace();
    return GridFunction<BasisFunctionType, ResultType>(
        g1.context(), // arbitrary choice...
        g1.space(), dualSpace,
        g1.projections(dualSpace) - g2.projections(dualSpace));
  }
}

template <typename BasisFunctionType, typename ResultType, typename ScalarType>
GridFunction<BasisFunctionType, ResultType>
operator*(const GridFunction<BasisFunctionType, ResultType> &g1,
          const ScalarType &scalar) {
  if (g1.wasInitializedFromCoefficients())
    return GridFunction<BasisFunctionType, ResultType>(
        g1.context(), g1.space(),
        static_cast<ResultType>(scalar) * g1.coefficients());
  else {
    shared_ptr<const Space<BasisFunctionType>> dualSpace = g1.dualSpace();
    return GridFunction<BasisFunctionType, ResultType>(
        g1.context(), g1.space(), dualSpace,
        static_cast<ResultType>(scalar) * g1.projections(dualSpace));
  }
}

BEMPP_GCC_DIAG_ON(deprecated - declarations);

FIBER_INSTANTIATE_CLASS_TEMPLATED_ON_BASIS_AND_RESULT(GridFunction);

#define INSTANTIATE_FREE_FUNCTIONS(BASIS, RESULT)                              \
  template GridFunction<BASIS, RESULT> operator+(                              \
      const GridFunction<BASIS, RESULT> &op);                                  \
  template GridFunction<BASIS, RESULT> operator-(                              \
      const GridFunction<BASIS, RESULT> &op);                                  \
  template GridFunction<BASIS, RESULT> operator+(                              \
      const GridFunction<BASIS, RESULT> &op1,                                  \
      const GridFunction<BASIS, RESULT> &op2);                                 \
  template GridFunction<BASIS, RESULT> operator-(                              \
      const GridFunction<BASIS, RESULT> &op1,                                  \
      const GridFunction<BASIS, RESULT> &op2);                                 \
  template void exportToVtk(const GridFunction<BASIS, RESULT> &gridFunction,   \
                            VtkWriter::DataType dataType,                      \
                            const char *dataLabel, const char *fileNamesBase,  \
                            const char *filesPath,                             \
                            VtkWriter::OutputType outputType)
#define INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(BASIS, RESULT, SCALAR)          \
  template GridFunction<BASIS, RESULT> operator*(                              \
      const GridFunction<BASIS, RESULT> &op, const SCALAR &scalar);            \
  template GridFunction<BASIS, RESULT> operator*(                              \
      const SCALAR &scalar, const GridFunction<BASIS, RESULT> &op);            \
  template GridFunction<BASIS, RESULT> operator/(                              \
      const GridFunction<BASIS, RESULT> &op, const SCALAR &scalar)

#if defined(ENABLE_SINGLE_PRECISION)
INSTANTIATE_FREE_FUNCTIONS(float, float);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(float, float, float);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(float, float, double);
// INSTANTIATE_FREE_FUNCTIONS_FOR_REAL_BASIS(float);
#endif

#if defined(ENABLE_SINGLE_PRECISION) &&                                        \
    (defined(ENABLE_COMPLEX_KERNELS) ||                                        \
     defined(ENABLE_COMPLEX_BASIS_FUNCTIONS))
INSTANTIATE_FREE_FUNCTIONS(float, std::complex<float>);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(float, std::complex<float>, float);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(float, std::complex<float>, double);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(float, std::complex<float>,
                                       std::complex<float>);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(float, std::complex<float>,
                                       std::complex<double>);
#endif

#if defined(ENABLE_SINGLE_PRECISION) && defined(ENABLE_COMPLEX_BASIS_FUNCTIONS)
INSTANTIATE_FREE_FUNCTIONS(std::complex<float>, std::complex<float>);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(std::complex<float>, std::complex<float>,
                                       float);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(std::complex<float>, std::complex<float>,
                                       double);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(std::complex<float>, std::complex<float>,
                                       std::complex<float>);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(std::complex<float>, std::complex<float>,
                                       std::complex<double>);
// INSTANTIATE_FREE_FUNCTIONS_FOR_COMPLEX_BASIS(std::complex<float>);
#endif

#if defined(ENABLE_DOUBLE_PRECISION)
INSTANTIATE_FREE_FUNCTIONS(double, double);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(double, double, float);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(double, double, double);
// INSTANTIATE_FREE_FUNCTIONS_FOR_REAL_BASIS(double);
#endif

#if defined(ENABLE_DOUBLE_PRECISION) &&                                        \
    (defined(ENABLE_COMPLEX_KERNELS) ||                                        \
     defined(ENABLE_COMPLEX_BASIS_FUNCTIONS))
INSTANTIATE_FREE_FUNCTIONS(double, std::complex<double>);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(double, std::complex<double>, float);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(double, std::complex<double>, double);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(double, std::complex<double>,
                                       std::complex<float>);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(double, std::complex<double>,
                                       std::complex<double>);
#endif

#if defined(ENABLE_DOUBLE_PRECISION) && defined(ENABLE_COMPLEX_BASIS_FUNCTIONS)
INSTANTIATE_FREE_FUNCTIONS(std::complex<double>, std::complex<double>);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(std::complex<double>,
                                       std::complex<double>, float);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(std::complex<double>,
                                       std::complex<double>, double);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(std::complex<double>,
                                       std::complex<double>,
                                       std::complex<float>);
INSTANTIATE_FREE_FUNCTIONS_WITH_SCALAR(std::complex<double>,
                                       std::complex<double>,
                                       std::complex<double>);
// INSTANTIATE_FREE_FUNCTIONS_FOR_COMPLEX_BASIS(std::complex<double>);
#endif

} // namespace Bempp


#include "FollowLeaderSOB.h"
#include "BookTools.h"
#include "OrderStateTracker.h"
#include "ModelTracker.h"

#include <boost/dynamic_bitset.hpp>
using boost::dynamic_bitset;

const int DEFAULT_TIMEOUT = 3600*8;
//const OrderPlacementSuggestion::PlacementReason FollowLeaderSOB::_default_reason = OrderPlacementSuggestion::FOLLOW_LEADER_SOB;
const OrderPlacementSuggestion::PlacementReason _defaultReason = OrderPlacementSuggestion::FOLLOW_LEADER_SOB;

const ECN::ECN ECNS_we_can_follow[] = { ECN::ISLD, ECN::BATS,ECN::ARCA,ECN::NYSE,ECN::BSX};

const int N_ECNS_we_can_follow = sizeof(ECNS_we_can_follow) / sizeof(ECN::ECN);
FollowLeaderSOB::FollowLeaderSOB() :
  _logPrinter( factory<debug_stream>::get(std::string("trader")) ),
  _sobRepo( factory<SOBOrderRepo>::get(componentId())),
  _fvSignal(factory<UCS1>::get(only::one)),
  _maxQSizeT( factory<MaxQueueSizeTracker>::get(_defaultReason) ),
  _modelTracker( factory<ModelTracker>::get(only::one))
  //_defaultReason(OrderPlacementSuggestion::FOLLOW_LEADER_SOB)
{
  TAEL_PRINTF(_logPrinter.get(), TAEL_ERROR, "Init FLW_SOB with defRes=%s",OrderPlacementSuggestion::PlacementReasonDesc[_defaultReason]);
}

bool FollowLeaderSOB::queuePositionUnfavorable( const Order* order )
{

  double normFV;
  // Switch between model and heuristic
  if ((order->ecn()==ECN::ISLD) && (_modelTracker->modelApplies(order->cid())))
    return _sobRepo->queuePositionUnfavorable(order,&normFV);
  if (order->ecn()==ECN::NYSE){
    SingleStockState *ss = _stocksState->getState(order->cid());
    double mid = ss->mid();
    // Adjust mid for signal, if present.
    double alpha = 0.0;
    if (_fvSignal == NULL || !_fvSignal->getAlpha(order->cid(), alpha))
      return false;
    if (isnan(alpha))
      return false;
    double fv = mid * (1.0 + alpha);
    if (HFUtils::moreAggressive(order->side(),order->price(),fv)){
      return true;
    }
    return false;
  }
  else{
    int currQSize = getMarketOrders( _dm->subBook(order->ecn()), order->cid(), order->side(), order->price() );
    if( currQSize == 0 )
      // this should never happen because we assume we have an outstanding order at this px
      // Returning false. We should probably cancel for a different reason
      return false;

    int maxQSize = _maxQSizeT -> getMaxQueueSize( order->id() );
    if( maxQSize <= 0 ) {
      // This should not happen
      TAEL_PRINTF(_logPrinter.get(), TAEL_ERROR,  "%-5s ERROR: in FollowLeaderComponentKH::queuePositionUnfavorable, max-Q-size for order Id %d "
			   "is 0", _dm->symbol(order->cid()), order->id() );
      return false;
    }

    // Try to guess the position of our order in the queue
    int positionInQ;
    if( !MaxQueueSizeTracker::guessPositionInQueue(_dm.get(), order, positionInQ) )
      // couldn't guess position
      return false;

    int ourPositionFromEnd = currQSize - positionInQ;
    if( (currQSize<maxQSize) && (((double)ourPositionFromEnd)/currQSize < K_ALLOWED_QUEUE_POSITION) ) {
      return true;
    }
    return false;

  }
  // not needed
  return false;
}

void FollowLeaderSOB::suggestOrderPlacements( int cid, Mkt::Side side, int tradeLogicId,
						    int numShares, double priority,
						    vector<OrderPlacementSuggestion> &suggestions ) {
  // Empty out suggestions vector in case it's being re-used.
  suggestions.clear();

  // No capacity allocated - no orders to place.
  if (numShares <= 0)
    return;

  // Check that specified priority/aggressiveness is high enough that we guesstimate that this trading algo will work.
  if( cmp<6>::LT(priority,expectedTC()) )
    return;

  // Don't follow on invalid market
  SingleStockState *ss;
  ss = _stocksState->getState(cid);
  if( !ss->haveNormalOrLockedMarket() )
    return;

  // Check if we got any followable leader orders for specified stock x side.
  bitset<ECN::ECN_size> ecns;
  double loPrice;
  bool afl = anyFollowableLeaders( cid, side, ecns, &loPrice );

  if( afl ) {
    ECN::ECN e = chooseECN( cid, side, tradeLogicId, numShares, priority, loPrice, ecns );
    if( e != ECN::UNKN ) {
      // Calculate order size.
      int osize = calculateOrderSize(cid, side, numShares, e, loPrice); // truncate big-odd-lots on NYSE to round-lots
      // Allocate logical order sequence number.  Used to group physical orders that represent smaller
      //   pieces of large logical order.
      int csqn = allocateSeqNum();
      // Break order up into (mostly) round-lot sized chunks to reduce information leakage.
      vector<int> chunkSizes;
      HFUtils::chunkOrderShares(osize, 100, chunkSizes);
      for (unsigned int i =0 ; i < chunkSizes.size(); i++) {
	if (chunkSizes[i] <= 0) continue;
	OrderPlacementSuggestion ops( cid, e, side, chunkSizes[i], loPrice, DEFAULT_TIMEOUT,
				      tradeLogicId, _componentId, csqn, _defaultReason,
				      _dm->curtv(), ss->bestPrice(Mkt::BID), ss->bestPrice(Mkt::ASK),
				      priority);
	suggestions.push_back(ops);
      }
    }
  }
  return;
}


#include <btcb/node/common.hpp>
#include <btcb/node/wallet.hpp>
#include <btcb/secure/blockstore.hpp>

#include <boost/polymorphic_cast.hpp>

btcb::summation_visitor::summation_visitor (btcb::transaction const & transaction_a, btcb::block_store & store_a) :
transaction (transaction_a),
store (store_a)
{
}

void btcb::summation_visitor::send_block (btcb::send_block const & block_a)
{
	assert (current->type != summation_type::invalid && current != nullptr);
	if (current->type == summation_type::amount)
	{
		sum_set (block_a.hashables.balance.number ());
		current->balance_hash = block_a.hashables.previous;
		current->amount_hash = 0;
	}
	else
	{
		sum_add (block_a.hashables.balance.number ());
		current->balance_hash = 0;
	}
}

void btcb::summation_visitor::state_block (btcb::state_block const & block_a)
{
	assert (current->type != summation_type::invalid && current != nullptr);
	sum_set (block_a.hashables.balance.number ());
	if (current->type == summation_type::amount)
	{
		current->balance_hash = block_a.hashables.previous;
		current->amount_hash = 0;
	}
	else
	{
		current->balance_hash = 0;
	}
}

void btcb::summation_visitor::receive_block (btcb::receive_block const & block_a)
{
	assert (current->type != summation_type::invalid && current != nullptr);
	if (current->type == summation_type::amount)
	{
		current->amount_hash = block_a.hashables.source;
	}
	else
	{
		btcb::block_info block_info;
		if (!store.block_info_get (transaction, block_a.hash (), block_info))
		{
			sum_add (block_info.balance.number ());
			current->balance_hash = 0;
		}
		else
		{
			current->amount_hash = block_a.hashables.source;
			current->balance_hash = block_a.hashables.previous;
		}
	}
}

void btcb::summation_visitor::open_block (btcb::open_block const & block_a)
{
	assert (current->type != summation_type::invalid && current != nullptr);
	if (current->type == summation_type::amount)
	{
		if (block_a.hashables.source != btcb::genesis_account)
		{
			current->amount_hash = block_a.hashables.source;
		}
		else
		{
			sum_set (btcb::genesis_amount);
			current->amount_hash = 0;
		}
	}
	else
	{
		current->amount_hash = block_a.hashables.source;
		current->balance_hash = 0;
	}
}

void btcb::summation_visitor::change_block (btcb::change_block const & block_a)
{
	assert (current->type != summation_type::invalid && current != nullptr);
	if (current->type == summation_type::amount)
	{
		sum_set (0);
		current->amount_hash = 0;
	}
	else
	{
		btcb::block_info block_info;
		if (!store.block_info_get (transaction, block_a.hash (), block_info))
		{
			sum_add (block_info.balance.number ());
			current->balance_hash = 0;
		}
		else
		{
			current->balance_hash = block_a.hashables.previous;
		}
	}
}

btcb::summation_visitor::frame btcb::summation_visitor::push (btcb::summation_visitor::summation_type type_a, btcb::block_hash const & hash_a)
{
	frames.emplace (type_a, type_a == summation_type::balance ? hash_a : 0, type_a == summation_type::amount ? hash_a : 0);
	return frames.top ();
}

void btcb::summation_visitor::sum_add (btcb::uint128_t addend_a)
{
	current->sum += addend_a;
	result = current->sum;
}

void btcb::summation_visitor::sum_set (btcb::uint128_t value_a)
{
	current->sum = value_a;
	result = current->sum;
}

btcb::uint128_t btcb::summation_visitor::compute_internal (btcb::summation_visitor::summation_type type_a, btcb::block_hash const & hash_a)
{
	push (type_a, hash_a);

	/*
	 Invocation loop representing balance and amount computations calling each other.
	 This is usually better done by recursion or something like boost::coroutine2, but
	 segmented stacks are not supported on all platforms so we do it manually to avoid
	 stack overflow (the mutual calls are not tail-recursive so we cannot rely on the
	 compiler optimizing that into a loop, though a future alternative is to do a
	 CPS-style implementation to enforce tail calls.)
	*/
	while (frames.size () > 0)
	{
		current = &frames.top ();
		assert (current->type != summation_type::invalid && current != nullptr);

		if (current->type == summation_type::balance)
		{
			if (current->awaiting_result)
			{
				sum_add (current->incoming_result);
				current->awaiting_result = false;
			}

			while (!current->awaiting_result && (!current->balance_hash.is_zero () || !current->amount_hash.is_zero ()))
			{
				if (!current->amount_hash.is_zero ())
				{
					// Compute amount
					current->awaiting_result = true;
					push (summation_type::amount, current->amount_hash);
					current->amount_hash = 0;
				}
				else
				{
					auto block (store.block_get (transaction, current->balance_hash));
					assert (block != nullptr);
					block->visit (*this);
				}
			}

			epilogue ();
		}
		else if (current->type == summation_type::amount)
		{
			if (current->awaiting_result)
			{
				sum_set (current->sum < current->incoming_result ? current->incoming_result - current->sum : current->sum - current->incoming_result);
				current->awaiting_result = false;
			}

			while (!current->awaiting_result && (!current->amount_hash.is_zero () || !current->balance_hash.is_zero ()))
			{
				if (!current->amount_hash.is_zero ())
				{
					auto block (store.block_get (transaction, current->amount_hash));
					if (block != nullptr)
					{
						block->visit (*this);
					}
					else
					{
						if (current->amount_hash == btcb::genesis_account)
						{
							sum_set (std::numeric_limits<btcb::uint128_t>::max ());
							current->amount_hash = 0;
						}
						else
						{
							assert (false);
							sum_set (0);
							current->amount_hash = 0;
						}
					}
				}
				else
				{
					// Compute balance
					current->awaiting_result = true;
					push (summation_type::balance, current->balance_hash);
					current->balance_hash = 0;
				}
			}

			epilogue ();
		}
	}

	return result;
}

void btcb::summation_visitor::epilogue ()
{
	if (!current->awaiting_result)
	{
		frames.pop ();
		if (frames.size () > 0)
		{
			frames.top ().incoming_result = current->sum;
		}
	}
}

btcb::uint128_t btcb::summation_visitor::compute_amount (btcb::block_hash const & block_hash)
{
	return compute_internal (summation_type::amount, block_hash);
}

btcb::uint128_t btcb::summation_visitor::compute_balance (btcb::block_hash const & block_hash)
{
	return compute_internal (summation_type::balance, block_hash);
}

btcb::representative_visitor::representative_visitor (btcb::transaction const & transaction_a, btcb::block_store & store_a) :
transaction (transaction_a),
store (store_a),
result (0)
{
}

void btcb::representative_visitor::compute (btcb::block_hash const & hash_a)
{
	current = hash_a;
	while (result.is_zero ())
	{
		auto block (store.block_get (transaction, current));
		assert (block != nullptr);
		block->visit (*this);
	}
}

void btcb::representative_visitor::send_block (btcb::send_block const & block_a)
{
	current = block_a.previous ();
}

void btcb::representative_visitor::receive_block (btcb::receive_block const & block_a)
{
	current = block_a.previous ();
}

void btcb::representative_visitor::open_block (btcb::open_block const & block_a)
{
	result = block_a.hash ();
}

void btcb::representative_visitor::change_block (btcb::change_block const & block_a)
{
	result = block_a.hash ();
}

void btcb::representative_visitor::state_block (btcb::state_block const & block_a)
{
	result = block_a.hash ();
}