// @Time : 2021/5/29 下午8:20
// @Author : PH
// @Version：V 0.1
// @File : generateKeyFrame.cpp
// @desc :
#include <iostream>
#include <opencv2/opencv.hpp>
#include <algorithm>
#include <numeric>
#include <boost/algorithm/string.hpp>
#include "getFiles.h"
using namespace std;
using namespace cv;
vector<Mat> getHistRGB(Mat& frame) {
  vector<Mat> hist_rgb(3);
  // 分离BGR通道
  Mat channels[3];
  split(frame, channels);
  // 分别计算每个通道的直方图
  const int bins[1] = {256};
  const float inranges[2] = {0, 255};
  const float* ranges = {inranges};
  calcHist(&channels[0], 1, 0, Mat(), hist_rgb[0], 1, bins, &ranges);
  calcHist(&channels[1], 1, 0, Mat(), hist_rgb[1], 1, bins, &ranges);
  calcHist(&channels[2], 1, 0, Mat(), hist_rgb[2], 1, bins, &ranges);
  return hist_rgb;
}

double autoThreshold(vector<double>& nums) {
  double mean = std::accumulate(nums.begin(), nums.end(), 0) / nums.size();
  double var = 0;
  for (auto num : nums) {
	var += pow(num - mean, 2);
  }
  var /= nums.size();
  double std = sqrt(var);
  return std + mean;
}

int main() {
  vector<string> video_paths;
  getFiles("/home/ph/Dataset/VideoCaption/test", video_paths);
  cout << "待处理视频数：" << video_paths.size() << endl;
  for (int i = 0; i < video_paths.size(); i++) {
	cout << "处理视频：" << i + 1 << " ";
	// 捕获一个视频
	string video_path = video_paths[i];
	vector<string> split_str;
	boost::split(split_str, video_path, boost::is_any_of("./"));
	string video_name = split_str[split_str.size() - 2];
	VideoCapture cap;
	cap.open(video_path);
	if (!cap.isOpened()) {
	  cout << "can not open video!" << endl;
	}

	// 视频信息
	int n_frame = cap.get(CAP_PROP_FRAME_COUNT);

	// 三帧直方图差分
	Mat frame1, frame2, cur_frame;
	vector<double> all_scores;
	for (int j = 0; j <= n_frame; j++) {
	  // 先获取前两帧
	  if (j == 0) {
		cap >> frame1;
		string name = "/home/ph/Dataset/VideoCaption/generateImgs/test/" + video_name + "-" + to_string(j) + ".jpg";
		imwrite(name, frame1);
		cout << "保存第" << j << "帧" << " ";
		continue;
	  }
	  if (j == 1) {
		cap >> frame2;
		continue;
	  }

	  // 从第2帧开始计算直方图差分的分数
	  cap >> cur_frame;
	  if (cur_frame.empty()) {
		break;
	  }
	  vector<Mat> A, B, C;
	  A = getHistRGB(frame1);
	  B = getHistRGB(frame2);
	  C = getHistRGB(cur_frame);

	  double score1 = 0, score2 = 0;
	  for (int k = 0; k < 3; k++) {
		score1 += compareHist(A[k], B[k], HISTCMP_INTERSECT);
		score2 += compareHist(B[k], C[k], HISTCMP_INTERSECT);
	  }
	  all_scores.push_back(abs(score2 - score1)/3);

	  // 以三帧为窗口，滑动窗口计算整个视频的差分结果
	  frame2.copyTo(frame1);
	  cur_frame.copyTo(frame2);
	}

	// 从视频的第二帧开始，根据全部差分结果和自适应阈值来确定关键帧
	double threshold = autoThreshold(all_scores);
	Mat key_frame;
	int cnt = 0;
	for (int k = 2; k <= n_frame; k++) {
	  if (all_scores[k - 1] > threshold) {
		cap.set(CAP_PROP_POS_FRAMES, k);
		cap >> key_frame;
		k++; // 一般来说第k帧和第k+1帧差不多，只要一个就行了
		cnt++; // 对一个视频最多只要8帧
		if (key_frame.empty() || cnt > 8) {
		  break;
		}
		string name = "/home/ph/Dataset/VideoCaption/generateImgs/test/" + video_name + "-" + to_string(k) + ".jpg";
		imwrite(name, key_frame);
		cout << "保存第" << k << "帧" << " ";
	  }
	}
	cout << endl;
	cap.release();
  }
  return 0;
}


#include <iostream>
#include <Eigen/Core>
using namespace Eigen;
using namespace std;

int main()
{
  typedef Matrix<SCALAR,Eigen::Dynamic,1> Vec;
  Vec v(SIZE);
  v.setZero();
  v[0] = 1;
  v[1] = 2;
  for(int i = 0; i < 1000000; i++)
  {
    v.coeffRef(0) += v.sum() * SCALAR(1e-20);
  }
  cout << v.sum() << endl;
}


#include <blitz/vector2.h>
#include <blitz/array.h>
#include <random/uniform.h>
#include <blitz/benchext.h>

#ifdef BZ_HAVE_VALARRAY
 #define BENCHMARK_VALARRAY
#endif

#ifdef BENCHMARK_VALARRAY
#include <valarray>
#endif

BZ_NAMESPACE(blitz)
extern void sink();
BZ_NAMESPACE_END

BZ_USING_NAMESPACE(blitz)

#if defined(BZ_FORTRAN_SYMBOLS_WITH_TRAILING_UNDERSCORES)
 #define fdaxpy   fdaxpy_
 #define daxpy    daxpy_
 #define f90daxpy f90daxpy_
 #define fidaxpy  fidaxpy_
 #define fidaxpyo fidaxpyo_
#elif defined(BZ_FORTRAN_SYMBOLS_WITH_DOUBLE_TRAILING_UNDERSCORES)
 #define fdaxpy   fdaxpy__
 #define daxpy    daxpy__
 #define f90daxpy f90daxpy__
 #define fidaxpy  fidaxpy__
 #define fidaxpyo fidaxpyo__
#elif defined(BZ_FORTRAN_SYMBOLS_CAPS)
 #define fdaxpy   FDAXPY
 #define daxpy    DAXPY
 #define f90daxpy F90DAXPY
 #define fidaxpy  FIDAXPY
 #define fidaxpyo FIDAXPYO
#endif

extern "C" {
  void fdaxpy(const int& N, const double& da, double* x,
    const int& xstride, const double* y, const int& ystride);

  void daxpy(const int& N, const double& da, double* x,
    const int& xstride, const double* y, const int& ystride);

  void f90daxpy(const double& a, double* x,
    const double* y, const int& length, const int& iters);

  void fidaxpy(const double& a, double* x, const double* y,
    const int& length, const int& iters);

  void fidaxpyo(const double& a, double* x, const double* y,
    const int& length, const int& iters);
}

void daxpyVectorVersion(BenchmarkExt<int>& bench, double a, double b);
void daxpyArrayVersion(BenchmarkExt<int>& bench, double a);
void daxpyF77Version(BenchmarkExt<int>& bench, double a);
void daxpyBLASVersion(BenchmarkExt<int>& bench, double a);
#ifdef FORTRAN_90
void daxpyF90Version(BenchmarkExt<int>& bench, double a);
#endif
#ifdef BENCHMARK_VALARRAY
void daxpyValarrayVersion(BenchmarkExt<int>& bench, double a);
#endif

int main()
{
    int numBenchmarks = 6;
#ifndef BENCHMARK_VALARRAY
		numBenchmarks--;   // No  valarray
#endif
#ifndef FORTRAN_90
		numBenchmarks--;   // No fortran 90
#endif

    BenchmarkExt<int> bench("DAXPY Benchmark", numBenchmarks);

    const int numSizes = 19;
    bench.setNumParameters(numSizes);

    Vector<int> parameters(numSizes);
    Vector<long> iters(numSizes);
    Vector<double> flops(numSizes);

    for (int i=0; i < numSizes; ++i)
    {
        parameters(i) = static_cast<int>(pow(10.0, 0.25*(i+1)));
        iters(i) = 50000000L / parameters(i);
        if (iters(i) < 2)
            iters(i) = 2;
        flops(i) = 2 * parameters(i) * 2;
    }

    bench.setParameterVector(parameters);
    bench.setIterations(iters);
    bench.setOpsPerIteration(flops);

    bench.beginBenchmarking();

    float a = .398498293819823;

    daxpyVectorVersion(bench, a, -a);
    daxpyArrayVersion(bench, a);
    daxpyF77Version(bench, a);
    daxpyBLASVersion(bench, a);
#ifdef FORTRAN_90
    daxpyF90Version(bench, a);
#endif
#ifdef BENCHMARK_VALARRAY
    daxpyValarrayVersion(bench, a);
#endif

    bench.endBenchmarking();

    bench.saveMatlabGraph("daxpy.m");

    return 0;
}

template<class T>
void initializeRandomDouble(T* data, int numElements, int stride = 1)
{
    ranlib::Uniform<T> rnd;

    for (int i=0; i < numElements; ++i)
        data[size_t(i*stride)] = rnd.random();
}

template<class T>
void initializeRandomDouble(valarray<T>& data, int numElements, int stride = 1)
{
    ranlib::Uniform<T> rnd;

    for (int i=0; i < numElements; ++i)
        data[size_t(i*stride)] = rnd.random();
}

void daxpyVectorVersion(BenchmarkExt<int>& bench, double a, double b)
{
    bench.beginImplementation("Vector<T>");

    while (!bench.doneImplementationBenchmark())
    {
        int N = bench.getParameter();

        cout << "Vector<T>: N = " << N << endl;
        cout.flush();

        long iters = bench.getIterations();

        Vector<double> x(N), y(N);
        initializeRandomDouble(x.data(), N);
        initializeRandomDouble(y.data(), N);

        bench.start();
        for (long i=0; i < iters; ++i)
        {
            y += a * x;
            y += b * x;
        }
        bench.stop();
    }

    bench.endImplementation();
}

void daxpyArrayVersion(BenchmarkExt<int>& bench, double a)
{
    bench.beginImplementation("Array<T,1>");

    while (!bench.doneImplementationBenchmark())
    {
        int N = bench.getParameter();

        cout << "Array<T,1>: N = " << N << endl;
        cout.flush();

        long iters = bench.getIterations();

        Array<double,1> x(N), y(N);
        initializeRandomDouble(x.data(), N);
        initializeRandomDouble(y.data(), N);

        double b = - a;

        bench.start();
        for (long i=0; i < iters; ++i)
        {
            y = y + a * x;
            y = y + b * x;
	    sink();
        }
        bench.stop();

        bench.startOverhead();
        for (long i=0; i < iters; ++i) {
            sink();
	}
        bench.stopOverhead();
    }

    bench.endImplementation();
}

void daxpyF77Version(BenchmarkExt<int>& bench, double a)
{
    bench.beginImplementation("Fortran 77");

    while (!bench.doneImplementationBenchmark())
    {
        int N = bench.getParameter();

        cout << "Fortran 77: N = " << N << endl;
        cout.flush();

        int iters = bench.getIterations();

        double* x = new double[N];
        double* y = new double[N];
        initializeRandomDouble(x, N);
        initializeRandomDouble(y, N);

        bench.start();
        fidaxpy(a, x, y, N, iters);
        bench.stop();

        delete [] x;
        delete [] y;
    }

    bench.endImplementation();
}

void daxpyBLASVersion(BenchmarkExt<int>& bench, double a)
{
#ifdef USE_LIBBLAS
    bench.beginImplementation("Platform BLAS");
#else
    bench.beginImplementation("Fortran BLAS");
#endif

    while (!bench.doneImplementationBenchmark())
    {
        int N = bench.getParameter();

        cout << "Fortran BLAS: N = " << N << endl;
        cout.flush();

        int iters = bench.getIterations();

        double* x = new double[N];
        double* y = new double[N];
        initializeRandomDouble(x, N);
        initializeRandomDouble(y, N);

        int xstride = 1, ystride = 1;
        double b = - a;

        bench.start();
        for (long i=0; i < iters; ++i)
        {
#ifdef USE_LIBBLAS
            daxpy(N, a, x, xstride, y, ystride);
            daxpy(N, b, x, xstride, y, ystride);
#else
            fdaxpy(N, a, x, xstride, y, ystride);
            fdaxpy(N, b, x, xstride, y, ystride);
#endif
        }
        bench.stop();

        delete [] x;
        delete [] y;
    }

    bench.endImplementation();
}

#ifdef FORTRAN_90
void daxpyF90Version(BenchmarkExt<int>& bench, double a)
{
    bench.beginImplementation("Fortran 90");

    while (!bench.doneImplementationBenchmark())
    {
        int N = bench.getParameter();

        cout << "Fortran 90: N = " << N << endl;
        cout.flush();

        int iters = bench.getIterations();

        double* x = new double[N];
        double* y = new double[N];
        initializeRandomDouble(x, N);
        initializeRandomDouble(y, N);

        bench.start();
        f90daxpy(a, x, y, N, iters);
        bench.stop();

        delete [] x;
        delete [] y;
    }

    bench.endImplementation();
}
#endif

#ifdef BENCHMARK_VALARRAY
void daxpyValarrayVersion(BenchmarkExt<int>& bench, double a)
{
    bench.beginImplementation("valarray<T>");

    while (!bench.doneImplementationBenchmark())
    {
        int N = bench.getParameter();

        cout << "valarray<T>: N = " << N << endl;
        cout.flush();

        long iters = bench.getIterations();

        valarray<double> x(N), y(N);
        initializeRandomDouble(x, N);
        initializeRandomDouble(y, N);

        double b = - a;

        bench.start();
        for (long i=0; i < iters; ++i)
        {
            y += a * x;
            y += b * x;
        }
        bench.stop();
    }

    bench.endImplementation();
}
#endif


// Copyright 2018 The Simons Foundation, Inc. - All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// by G. Mazzola, May-Aug 2018

#include <Eigen/Dense>
#include <iostream>
#include <vector>
#include "Utils/all_utils.hpp"
#include "Utils/lookup.hpp"

#ifndef NETKET_JASTROW_HPP
#define NETKET_JASTROW_HPP

namespace netket {

/** Jastrow machine class.
 *
 */
template <typename T>
class Jastrow : public AbstractMachine<T> {
  using VectorType = typename AbstractMachine<T>::VectorType;
  using MatrixType = typename AbstractMachine<T>::MatrixType;
  using VectorRefType = typename AbstractMachine<T>::VectorRefType;
  using VectorConstRefType = typename AbstractMachine<T>::VectorConstRefType;
  using VisibleConstType = typename AbstractMachine<T>::VisibleConstType;

  const AbstractHilbert &hilbert_;

  // number of visible units
  int nv_;

  // number of parameters
  int npar_;

  // weights
  MatrixType W_;

  // buffers
  VectorType thetas_;
  VectorType thetasnew_;

 public:
  using StateType = typename AbstractMachine<T>::StateType;
  using LookupType = typename AbstractMachine<T>::LookupType;

  // constructor
  explicit Jastrow(const AbstractHilbert &hilbert)
      : hilbert_(hilbert), nv_(hilbert.Size()) {
    Init();
  }

  void Init() {
    if (nv_ < 2) {
      throw InvalidInputError(
          "Cannot construct Jastrow states with less than two visible units");
    }

    W_.resize(nv_, nv_);
    W_.setZero();

    npar_ = (nv_ * (nv_ - 1)) / 2;

    thetas_.resize(nv_);
    thetasnew_.resize(nv_);

    InfoMessage() << "Jastrow WF Initizialized with nvisible = " << nv_
                  << " and nparams = " << npar_ << std::endl;
  }

  int Nvisible() const override { return nv_; }

  int Npar() const override { return npar_; }

  void InitRandomPars(int seed, double sigma) override {
    VectorType par(npar_);

    netket::RandomGaussian(par, seed, sigma);

    SetParameters(par);
  }

  VectorType GetParameters() override {
    VectorType pars(npar_);

    int k = 0;

    for (int i = 0; i < nv_; i++) {
      for (int j = i + 1; j < nv_; j++) {
        pars(k) = W_(i, j);
        k++;
      }
    }

    return pars;
  }

  void SetParameters(VectorConstRefType pars) override {
    int k = 0;

    for (int i = 0; i < nv_; i++) {
      W_(i, i) = T(0.);
      for (int j = i + 1; j < nv_; j++) {
        W_(i, j) = pars(k);
        W_(j, i) = W_(i, j);  // create the lower triangle
        k++;
      }
    }
  }

  void InitLookup(VisibleConstType v, LookupType &lt) override {
    if (lt.VectorSize() == 0) {
      lt.AddVector(v.size());
    }
    if (lt.V(0).size() != v.size()) {
      lt.V(0).resize(v.size());
    }

    lt.V(0) = (W_.transpose() * v);  // does not matter the transpose W is symm
  }

  // same as for the RBM
  void UpdateLookup(VisibleConstType v, const std::vector<int> &tochange,
                    const std::vector<double> &newconf,
                    LookupType &lt) override {
    if (tochange.size() != 0) {
      for (std::size_t s = 0; s < tochange.size(); s++) {
        const int sf = tochange[s];
        lt.V(0) += W_.row(sf) * (newconf[s] - v(sf));
      }
    }
  }

  T LogVal(VisibleConstType v) override { return 0.5 * v.dot(W_ * v); }

  // Value of the logarithm of the wave-function
  // using pre-computed look-up tables for efficiency
  T LogVal(VisibleConstType v, const LookupType &lt) override {
    return 0.5 * v.dot(lt.V(0));
  }

  // Difference between logarithms of values, when one or more visible variables
  // are being flipped
  VectorType LogValDiff(
      VisibleConstType v, const std::vector<std::vector<int>> &tochange,
      const std::vector<std::vector<double>> &newconf) override {
    const std::size_t nconn = tochange.size();
    VectorType logvaldiffs = VectorType::Zero(nconn);

    thetas_ = (W_.transpose() * v);
    T logtsum = 0.5 * v.dot(thetas_);

    for (std::size_t k = 0; k < nconn; k++) {
      if (tochange[k].size() != 0) {
        thetasnew_ = thetas_;
        Eigen::VectorXd vnew(v);

        for (std::size_t s = 0; s < tochange[k].size(); s++) {
          const int sf = tochange[k][s];

          thetasnew_ += W_.row(sf) * (newconf[k][s] - v(sf));
          vnew(sf) = newconf[k][s];
        }

        logvaldiffs(k) = 0.5 * vnew.dot(thetasnew_) - logtsum;
      }
    }
    return logvaldiffs;
  }

  T LogValDiff(VisibleConstType v, const std::vector<int> &tochange,
               const std::vector<double> &newconf,
               const LookupType &lt) override {
    T logvaldiff = 0.;

    if (tochange.size() != 0) {
      T logtsum = 0.5 * v.dot(lt.V(0));
      thetasnew_ = lt.V(0);
      Eigen::VectorXd vnew(v);

      for (std::size_t s = 0; s < tochange.size(); s++) {
        const int sf = tochange[s];

        thetasnew_ += W_.row(sf) * (newconf[s] - v(sf));
        vnew(sf) = newconf[s];
      }

      logvaldiff = 0.5 * vnew.dot(thetasnew_) - logtsum;
    }

    return logvaldiff;
  }

  VectorType DerLog(VisibleConstType v) override {
    VectorType der(npar_);

    int k = 0;

    for (int i = 0; i < nv_; i++) {
      for (int j = i + 1; j < nv_; j++) {
        der(k) = v(i) * v(j);
        k++;
      }
    }

    return der;
  }

  const AbstractHilbert &GetHilbert() const noexcept override {
    return hilbert_;
  }

  void to_json(json &j) const override {
    j["Name"] = "Jastrow";
    j["Nvisible"] = nv_;
    j["W"] = W_;
  }

  void from_json(const json &pars) override {
    if (pars.at("Name") != "Jastrow") {
      throw InvalidInputError(
          "Error while constructing Jastrow from Json input");
    }

    if (FieldExists(pars, "Nvisible")) {
      nv_ = pars["Nvisible"];
    }
    if (nv_ != hilbert_.Size()) {
      throw InvalidInputError(
          "Number of visible units is incompatible with given "
          "Hilbert space");
    }

    Init();

    if (FieldExists(pars, "W")) {
      W_ = pars["W"];
    }
  }
};
}  // namespace netket

#endif


// Copyright (c) 2020 Marcus Valtonen Örnhag
//
// 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 <Eigen/Dense>
#include <math.h>  // copysign

#include "radial.hpp"

namespace DronePoseLib {
    Eigen::MatrixXd radialdistort(const Eigen::MatrixXd& x, double kappa) {
        // We expect inhomogenous input data
        assert(x.rows() == 2);

        Eigen::ArrayXd ru2 = x.colwise().squaredNorm();
        Eigen::ArrayXd ru = ru2.sqrt();

        Eigen::ArrayXd rd;
        // Compute distorted radius
        if (kappa == 0) {
            rd = ru;
        } else {
            rd = 0.5 / kappa / ru - copysign(1.0, kappa) * (0.25 / std::pow(kappa, 2) / ru2 - 1.0 / kappa).sqrt();
        }

        // compute distorted coordinates
        Eigen::MatrixXd y(2, x.cols());
        y = (rd / ru).replicate(1, x.rows()).transpose() * x.array();

        // TODO(marcusvaltonen): Avoid divion by zero (centre coordinate - usually synthethic images)
        // y(isnan(y(:))) = 0;

        return y;
    }

    Eigen::MatrixXd radialundistort(const Eigen::MatrixXd& x, double kappa) {
        // We expect inhomogenous input data
        assert(x.rows() == 2);

        Eigen::VectorXd rd2 = x.colwise().squaredNorm();

        // Compute undistorted coordinates
        Eigen::MatrixXd y(3, x.cols());
        y.topRows(2) = x;
        y.bottomRows(1) = (Eigen::VectorXd::Ones(x.cols()) + kappa * rd2).transpose();

        return y.colwise().hnormalized();
    }
}  // namespace DronePoseLib


/**
* \file compressor.cc
*
* \author Tobias Waurick
* \date 11.07.17
*
*/
#include "compressor.h"
#include <armadillo>
#include "assert.h"

NS_LOG_COMPONENT_DEFINE("Compressor");

/*--------  Compressor  --------*/

NS_OBJECT_ENSURE_REGISTERED(Compressor);

TypeId Compressor::GetTypeId(void)
{
	static TypeId tid = TypeId("Compressor")
							.SetParent<Object>()
							.AddConstructor<Compressor>()
							.SetGroupName("CompressedSensing")
							.AddAttribute("RanMatrix", "The underlying random matrix form to create the sensing matrix",
										  PointerValue(CreateObject<GaussianRandomMatrix>()),
										  MakePointerAccessor(&Compressor::SetRanMat, &Compressor::GetRanMat),
										  MakePointerChecker<RandomMatrix>())
							.AddAttribute("TransMatrix", "The underlying matrix of a real transformation in which the solution is sparse",
										  TypeId::ATTR_SET | TypeId::ATTR_CONSTRUCT,
										  PointerValue(),
										  MakePointerAccessor(&Compressor::SetTransMat),
										  MakePointerChecker<TransMatrix>())
							.AddTraceSource("Complete",
											"Trace source indicating that compression completed",
											MakeTraceSourceAccessor(&Compressor::m_completeCb),
											"Compressor::CompleteCallback");
	return tid;
}

Compressor::Compressor() : m_seed(1), m_m(0),
							  m_n(0), m_vecLen(0),
							  m_bufLenIn(0), m_bufLenOut(0)
{
}

Compressor::Compressor(uint32_t n, uint32_t m, uint32_t vecLen) : m_seed(1), m_m(m),
																	 m_n(n), m_vecLen(vecLen),
																	 m_bufLenIn(n * vecLen), m_bufLenOut(m * vecLen)

{
	if (m_transMat.isValid())
		m_transMat->SetSize(n);

	if (m_ranMat.isValid())
		m_ranMat->SetSize(m, n, m_seed);
}

void Compressor::Setup(uint32_t seed, uint32_t n, uint32_t m, uint32_t vecLen)
{
	NS_LOG_FUNCTION(this << seed << m << n << vecLen);
	m_seed = seed;
	m_m = m;
	m_n = n;
	m_vecLen = vecLen;
	m_bufLenIn = vecLen * n;
	m_bufLenOut = vecLen * m;

}

void Compressor::Compress(const double *bufferIn, uint32_t bufLenIn, double *bufferOut, uint32_t bufLenOut) const
{
	NS_LOG_FUNCTION(this << bufferIn << bufLenIn << bufferOut << bufLenOut);
	NS_ASSERT_MSG(bufLenIn == m_bufLenIn, "Incorrect input buffer size!");
	NS_ASSERT(bufferIn); //null pointer check

	const arma::Mat<double> x(const_cast<double *>(bufferIn), m_n, m_vecLen, false);
	Compress(x, bufferOut, bufLenOut);
}

void Compressor::Compress(const arma::Mat<double> &matIn, double *bufferOut, uint32_t bufLenOut) const
{
	NS_LOG_FUNCTION(this << matIn << bufferOut << bufLenOut);
	NS_ASSERT_MSG(bufLenOut == m_bufLenOut, "Incorrect output buffer size!");
	NS_ASSERT_MSG((matIn.n_rows == m_n) && (matIn.n_cols == m_vecLen), "Incorrect matrix size!");
	NS_ASSERT(bufferOut); //null pointer check

	if (m_transMat.isValid())
		m_transMat->SetSize(m_n);

	if (m_ranMat.isValid())
		m_ranMat->SetSize(m_m, m_n, m_seed);

	klab::TSmartPointer<kl1p::TOperator<double>> op_ptr = m_ranMat * m_transMat;
	arma::Mat<double> y(bufferOut, m_m, m_vecLen, false, true);

	for (uint32_t i = 0; i < m_vecLen; i++)
	{
		Col<double> yVec(m_m);
		op_ptr->apply(matIn.col(i), yVec);
		y.col(i) = yVec;
	}
m_completeCb(matIn, y);
}

template <typename TI>
void Compressor::CompressSparse(const arma::Mat<double> &data, const arma::Col<TI> &idx, double *bufferOut, uint32_t bufLenOut) const
{
	NS_LOG_FUNCTION(this << data << idx << bufferOut << bufLenOut);
	NS_ASSERT(data.n_rows == idx.n_rows);
	NS_ASSERT_MSG((data.n_rows <= m_n) && (data.n_cols == m_vecLen), "Size mismatch!");

	arma::Mat<double> sp = arma::zeros<arma::Mat<double>>(m_n, m_vecLen);
	//arma::SpMat<double> sp(m_m, m_n); // contains zeros by default
	for (uint32_t i = 0; i < idx.n_elem; i++)
	{
		uint32_t rowIdx = idx.at(i);
		sp.row(rowIdx) = data.row(i);
	}

	Compress(sp, bufferOut, bufLenOut);
}

void Compressor::SetSeed(uint32_t seed)
{
	m_seed = seed;
	if (m_ranMat.isValid())
	{
		m_ranMat->Generate(seed);
	}
}

void Compressor::SetRanMat(Ptr<RandomMatrix> ranMat_ptr)
{
	m_ranMat = ranMat_ptr->Clone();

	if (m_ranMat.isValid())
		m_ranMat->SetSize(m_m, m_n, m_seed);
}

Ptr<RandomMatrix>  Compressor::GetRanMat() const
{
	return Ptr<RandomMatrix>(m_ranMat.get());
}

void Compressor::SetTransMat(Ptr<TransMatrix> transMat_ptr)
{
	m_transMat = transMat_ptr->Clone();

	if (m_transMat.isValid())
		m_transMat->SetSize(m_n);
}

template void Compressor::CompressSparse<uint8_t>(const arma::Mat<double> &data, const arma::Col<uint8_t> &idx, double *bufferOut, uint32_t bufLenOut) const;
template void Compressor::CompressSparse<uint16_t>(const arma::Mat<double> &data, const arma::Col<uint16_t> &idx, double *bufferOut, uint32_t bufLenOut) const;
template void Compressor::CompressSparse<uint32_t>(const arma::Mat<double> &data, const arma::Col<uint32_t> &idx, double *bufferOut, uint32_t bufLenOut) const;

/*--------  CompressorTemp  --------*/

NS_OBJECT_ENSURE_REGISTERED(CompressorTemp);

TypeId CompressorTemp::GetTypeId(void)
{
	static TypeId tid = TypeId("CompressorTemp")
							.SetParent<Compressor>()
							.AddConstructor<CompressorTemp>()
							.SetGroupName("CompressedSensing");
	return tid;
}

CompressorTemp::CompressorTemp() : Compressor()
{
}

CompressorTemp::CompressorTemp(uint32_t n, uint32_t m) : Compressor(n, m, VECLEN)
{
}

void CompressorTemp::Setup(uint32_t seed, uint32_t n, uint32_t m)
{
	Compressor::Setup(seed, n, m, VECLEN);
}


#pragma once

#include <warpcoil/block.hpp>
#include <warpcoil/types.hpp>
#include <warpcoil/comma_separator.hpp>
#include <boost/lexical_cast.hpp>
#include <silicium/sink/ptr_sink.hpp>

namespace warpcoil
{
    namespace javascript
    {
        inline size_t find_number_of_bytes_required(std::uint64_t const maximum)
        {
            if (maximum < 0x100)
            {
                return 1;
            }
            if (maximum < 0x10000)
            {
                return 2;
            }
            if (maximum < 0x100000000)
            {
                return 4;
            }
            return 8;
        }

        template <class CharSink>
        void generate_integer_parser(CharSink &&code, indentation_level indentation,
                                     std::size_t const number_of_bytes)
        {
            bool const return_64_bit = (number_of_bytes > 4);
            if (return_64_bit)
            {
                Si::append(code, "function (minimum_high, minimum_low, "
                                 "maximum_high, maximum_low)\n");
            }
            else
            {
                Si::append(code, "function (minimum, maximum)\n");
            }
            block(code, indentation,
                  [&](indentation_level const in_function)
                  {
                      if (return_64_bit)
                      {
                          start_line(code, in_function, "var result_high = 0;\n");
                          start_line(code, in_function, "var result_low = 0;\n");
                      }
                      else
                      {
                          start_line(code, in_function, "var result = 0;\n");
                      }
                      start_line(code, in_function, "var parsed = 0;\n");
                      start_line(code, in_function, "return function (input)\n");
                      block(
                          code, in_function,
                          [&](indentation_level const in_result)
                          {
                              if (return_64_bit)
                              {
                                  start_line(code, in_result, "if (parsed < 4)\n");
                                  block(code, in_result,
                                        [&](indentation_level const in_if)
                                        {
                                            start_line(code, in_if, "result_high <<= 8;\n");
                                            start_line(code, in_if, "result_high |= input;\n");
                                        },
                                        "\n");
                                  start_line(code, in_result, "else\n");
                                  block(code, in_result,
                                        [&](indentation_level const in_else)
                                        {
                                            start_line(code, in_else, "result_low <<= 8;\n");
                                            start_line(code, in_else, "result_low |= input;\n");
                                        },
                                        "\n");
                              }
                              else
                              {
                                  if (number_of_bytes > 1)
                                  {
                                      start_line(code, in_result, "result <<= 8;\n");
                                  }
                                  start_line(code, in_result, "result |= input;\n");
                              }
                              start_line(code, in_result, "++parsed;\n");
                              start_line(code, in_result, "if (parsed === ",
                                         boost::lexical_cast<std::string>(number_of_bytes), ")\n");
                              block(code, in_result,
                                    [&](indentation_level const in_if)
                                    {
                                        if (return_64_bit)
                                        {
                                            // TODO range check
                                            start_line(code, in_if, "return {high: result_high, "
                                                                    "low: result_low};\n");
                                        }
                                        else
                                        {
                                            start_line(code, in_if, "if ((result < minimum) || "
                                                                    "(result > maximum))\n");
                                            block(code, in_if,
                                                  [&](indentation_level const in_check)
                                                  {
                                                      start_line(code, in_check, "throw new "
                                                                                "Error(\"received "
                                                                                "integer out of "
                                                                                "range\");\n");
                                                  },
                                                  "\n");
                                            start_line(code, in_if, "return result;\n");
                                        }
                                    },
                                    "\n");
                              start_line(code, in_result, "return undefined;\n");
                          },
                          ";\n");
                  },
                  "");
        }

        template <class CharSink>
        void generate_response_parsing(CharSink &&code, indentation_level indentation,
                                       Si::memory_range const common_library)
        {
            start_line(code, indentation, "var request_id_parser = ", common_library,
                       ".parse_int_8(0, 0, 0xffffffff, 0xffffffff);\n");
            start_line(code, indentation, "state = function (input)\n");
            block(code, indentation,
                  [&](indentation_level const in_state)
                  {
                      start_line(code, in_state, "var status = request_id_parser(input);\n");
                      start_line(code, in_state, "if (status === undefined)\n");
                      block(code, in_state,
                            [&](indentation_level const in_if)
                            {
                                start_line(code, in_if, "return;\n");
                            },
                            "\n");
                      start_line(code, in_state, "var key = JSON.stringify(status);\n");
                      start_line(code, in_state, "var pending_request = pending_requests[key];\n");
                      start_line(code, in_state, "if (pending_request === undefined)\n");
                      block(code, in_state,
                            [&](indentation_level const in_if)
                            {
                                start_line(code, in_if, "throw new "
                                                        "Error(\"Unexpected "
                                                        "response: \" + key);\n");
                            },
                            "\n");
                      start_line(code, in_state, "delete pending_requests[key];\n");
                      start_line(code, in_state, "var argument_parser = "
                                                 "pending_request.argument_"
                                                 "parser;\n");
                      // empty tuple requires no parser, so the field is undefined
                      start_line(code, in_state, "if (argument_parser === undefined)\n");
                      block(code, in_state,
                            [&](indentation_level const in_if)
                            {
                                start_line(code, in_if, "state = initial_state;\n");
                                start_line(code, in_if,
                                           "pending_request.on_result(undefined, []);\n");
                                start_line(code, in_if, "return;\n");
                            },
                            "\n");
                      start_line(code, in_state, "state = function (input)\n");
                      block(code, in_state,
                            [&](indentation_level const in_state)
                            {
                                start_line(code, in_state,
                                           "var parsed = argument_parser(input);\n");
                                start_line(code, in_state, "if (parsed !== undefined)\n");
                                block(code, in_state,
                                      [&](indentation_level const in_if)
                                      {
                                          start_line(code, in_if, "state = initial_state;\n");
                                          start_line(code, in_if, "pending_request.on_result("
                                                                  "undefined, parsed);\n");
                                          start_line(code, in_if, "return;\n");
                                      },
                                      "\n");
                            },
                            "\n");
                  },
                  ";\n");
        }

        template <class CharSink>
        void generate_parser_creation(CharSink &&code, indentation_level const indentation,
                                      types::type const &parsed, Si::memory_range const library)
        {
            Si::visit<void>(
                parsed,
                [&](types::integer const range)
                {
                    Si::append(code, library);
                    switch (find_number_of_bytes_required(range.maximum))
                    {
                    case 1:
                        Si::append(code, ".parse_int_1(");
                        Si::append(code, boost::lexical_cast<std::string>(range.minimum));
                        Si::append(code, ", ");
                        Si::append(code, boost::lexical_cast<std::string>(range.maximum));
                        break;

                    case 2:
                        Si::append(code, ".parse_int_2(");
                        Si::append(code, boost::lexical_cast<std::string>(range.minimum));
                        Si::append(code, ", ");
                        Si::append(code, boost::lexical_cast<std::string>(range.maximum));
                        break;

                    case 4:
                        Si::append(code, ".parse_int_4(");
                        Si::append(code, boost::lexical_cast<std::string>(range.minimum));
                        Si::append(code, ", ");
                        Si::append(code, boost::lexical_cast<std::string>(range.maximum));
                        break;

                    case 8:
                        Si::append(code, ".parse_int_8(");
                        Si::append(code, boost::lexical_cast<std::string>(range.minimum >> 32u));
                        Si::append(code, ", ");
                        Si::append(code,
                                   boost::lexical_cast<std::string>(range.minimum & 0xffffffffu));
                        Si::append(code, ", ");
                        Si::append(code, boost::lexical_cast<std::string>(range.maximum >> 32u));
                        Si::append(code, ", ");
                        Si::append(code,
                                   boost::lexical_cast<std::string>(range.maximum & 0xffffffffu));
                        break;

                    default:
                        SILICIUM_UNREACHABLE();
                    }
                    Si::append(code, ")");
                },
                [&](std::unique_ptr<types::variant> const &)
                {
                    throw std::logic_error("todo");
                },
                [&](std::unique_ptr<types::tuple> const &root)
                {
                    if (root->elements.empty())
                    {
                        Si::append(code, "undefined");
                    }
                    else
                    {
                        Si::append(code, "function ()\n");
                        block(
                            code, indentation,
                            [&](indentation_level const in_function)
                            {
                                start_line(code, in_function, "var elements = [];\n");
                                start_line(code, in_function, "var current_parser = ");
                                generate_parser_creation(code, in_function, root->elements[0],
                                                         library);
                                Si::append(code, ";\n");
                                start_line(code, in_function, "return function (input)\n");
                                block(
                                    code, in_function,
                                    [&](indentation_level const in_function_2)
                                    {
                                        start_line(code, in_function_2,
                                                   "switch (elements.length)\n");
                                        block(
                                            code, in_function_2,
                                            [&](indentation_level const in_switch)
                                            {
                                                for (size_t i = 0; i < root->elements.size(); ++i)
                                                {
                                                    start_line(code, in_switch, "case ",
                                                               boost::lexical_cast<std::string>(i),
                                                               ":\n");
                                                    block(
                                                        code, in_switch,
                                                        [&](indentation_level const in_case)
                                                        {
                                                            start_line(code, in_case,
                                                                       "var status = "
                                                                       "current_"
                                                                       "parser(input);"
                                                                       "\n");
                                                            start_line(code, in_case,
                                                                       "if (status "
                                                                       "=== "
                                                                       "undefined)\n");
                                                            block(code, in_case,
                                                                  [&](indentation_level const in_if)
                                                                  {
                                                                      start_line(code, in_if,
                                                                                "return"
                                                                                " undef"
                                                                                "ined;"
                                                                                "\n");
                                                                  },
                                                                  "\n");
                                                            start_line(code, in_case,
                                                                       "elements.push("
                                                                       "status);\n");
                                                            if (i == (root->elements.size() - 1))
                                                            {
                                                                start_line(code, in_case,
                                                                           "return "
                                                                           "elements;"
                                                                           "\n");
                                                            }
                                                            else
                                                            {
                                                                start_line(code, in_case, "current_"
                                                                                "parser "
                                                                                "= ");
                                                                generate_parser_creation(
                                                                    code, in_case,
                                                                    root->elements[i + 1], library);
                                                                Si::append(code, ";\n");
                                                                start_line(code, in_case,
                                                                           "break;\n");
                                                            }
                                                        },
                                                        "\n");
                                                }
                                            },
                                            "\n");
                                    },
                                    ";\n");
                            },
                            "()");
                    }
                },
                [&](std::unique_ptr<types::vector> const &)
                {
                    throw std::logic_error("todo");
                },
                [&](types::utf8 const text)
                {
                    Si::append(code, "function ()\n");
                    block(code, indentation,
                          [&](indentation_level const in_function)
                          {
                              start_line(code, in_function, "var length = ");
                              generate_parser_creation(code, in_function, text.code_units, library);
                              Si::append(code, ";\n");
                              start_line(code, in_function, "var content;\n");
                              start_line(code, in_function, "var received_content = 0;\n");
                              start_line(code, in_function, "return function (input)\n");
                              block(code, in_function,
                                    [&](indentation_level const in_function_2)
                                    {
                                        start_line(code, in_function_2,
                                                   "if (content === undefined)\n");
                                        block(code, in_function_2,
                                              [&](indentation_level const in_if)
                                              {
                                                  start_line(code, in_if, "var status = "
                                                                          "length(input);\n");
                                                  start_line(code, in_if, "if (status === "
                                                                          "undefined) { return; "
                                                                          "}\n");
                                                  start_line(code, in_if, "content = new "
                                                                          "ArrayBuffer(status);"
                                                                          "\n");
                                              },
                                              "\n");
                                        start_line(code, in_function_2, "else\n");
                                        block(code, in_function_2,
                                              [&](indentation_level const in_else)
                                              {
                                                  start_line(code, in_else, "new "
                                                                            "Uint8Array(content)["
                                                                            "received_content] = "
                                                                            "input;\n");
                                                  start_line(code, in_else,
                                                             "++received_content;\n");
                                                  start_line(code, in_else, "if (received_content "
                                                                            "!== "
                                                                            "content.byteLength) { "
                                                                            "return; }\n");
                                                  start_line(code, in_else, "var result = \"\";\n");
                                                  // TODO: parse UTF-8
                                                  start_line(code, in_else,
                                                             "for (var i = 0; i < "
                                                             "content.byteLength; ++i) "
                                                             "{ result += "
                                                             "String.fromCharCode(new "
                                                             "Uint8Array(content)[i]); }\n");
                                                  start_line(code, in_else, "return result;\n");
                                              },
                                              "\n");
                                    },
                                    ";\n");
                          },
                          "()");
                },
                [&](std::unique_ptr<types::structure> const &root)
                {
                    if (root->elements.empty())
                    {
                        Si::append(code, "undefined");
                    }
                    else
                    {
                        Si::append(code, "function ()\n");
                        block(
                            code, indentation,
                            [&](indentation_level const in_function)
                            {
                                start_line(code, in_function, "var result = {};\n");
                                start_line(code, in_function, "var members_parsed = 0;\n");
                                start_line(code, in_function, "var current_parser = ");
                                generate_parser_creation(code, in_function, root->elements[0].what,
                                                         library);
                                Si::append(code, ";\n");
                                start_line(code, in_function, "return function (input)\n");
                                block(
                                    code, in_function,
                                    [&](indentation_level const in_function_2)
                                    {
                                        start_line(code, in_function_2,
                                                   "switch (members_parsed)\n");
                                        block(
                                            code, in_function_2,
                                            [&](indentation_level const in_switch)
                                            {
                                                for (size_t i = 0; i < root->elements.size(); ++i)
                                                {
                                                    start_line(code, in_switch, "case ",
                                                               boost::lexical_cast<std::string>(i),
                                                               ":\n");
                                                    block(
                                                        code, in_switch,
                                                        [&](indentation_level const in_case)
                                                        {
                                                            start_line(code, in_case,
                                                                       "var status = "
                                                                       "current_"
                                                                       "parser(input);"
                                                                       "\n");
                                                            start_line(code, in_case,
                                                                       "if (status "
                                                                       "=== "
                                                                       "undefined)\n");
                                                            block(code, in_case,
                                                                  [&](indentation_level const in_if)
                                                                  {
                                                                      start_line(code, in_if,
                                                                                "return"
                                                                                " undef"
                                                                                "ined;"
                                                                                "\n");
                                                                  },
                                                                  "\n");
                                                            start_line(code, in_case, "result.",
                                                                       root->elements[i].name,
                                                                       " = status;\n");
                                                            start_line(code, in_case, "++members_"
                                                                                "parsed;\n");
                                                            if (i == (root->elements.size() - 1))
                                                            {
                                                                start_line(code, in_case, "return "
                                                                                "result;"
                                                                                "\n");
                                                            }
                                                            else
                                                            {
                                                                start_line(code, in_case, "current_"
                                                                                "parser "
                                                                                "= ");
                                                                generate_parser_creation(
                                                                    code, in_case,
                                                                    root->elements[i + 1].what,
                                                                    library);
                                                                Si::append(code, ";\n");
                                                                start_line(code, in_case,
                                                                           "break;\n");
                                                            }
                                                        },
                                                        "\n");
                                                }
                                            },
                                            "\n");
                                    },
                                    ";\n");
                            },
                            "()");
                    }
                });
        }

        template <class CharSink>
        void generate_size_of_value(CharSink &&code, indentation_level indentation,
                                    types::type const &type, Si::memory_range const value,
                                    Si::memory_range const library)
        {
            Si::visit<void>(
                type,
                [&](types::integer const range)
                {
                    Si::append(code, boost::lexical_cast<std::string>(
                                         find_number_of_bytes_required(range.maximum)));
                },
                [&](std::unique_ptr<types::variant> const &)
                {
                    throw std::logic_error("todo");
                },
                [&](std::unique_ptr<types::tuple> const &root)
                {
                    Si::append(code, "(0");
                    for (size_t i = 0, c = root->elements.size(); i < c; ++i)
                    {
                        Si::append(code, " + ");
                        std::string element_value;
                        element_value.append(value.begin(), value.size());
                        element_value += "[";
                        element_value += boost::lexical_cast<std::string>(i);
                        element_value += "]";
                        generate_size_of_value(code, indentation, root->elements[i],
                                               Si::make_memory_range(element_value), library);
                    }
                    Si::append(code, ")");
                },
                [&](std::unique_ptr<types::vector> const &root)
                {
                    Si::append(code, "(");
                    Si::append(code, boost::lexical_cast<std::string>(
                                         find_number_of_bytes_required(root->length.maximum)));
                    Si::append(code, " + ");
                    Si::append(code, value);
                    Si::append(code, ".reduce(function (previous, next) { "
                                     "return previous + next; }, 0))");
                },
                [&](types::utf8 const text)
                {
                    Si::append(code, "(");
                    Si::append(code, boost::lexical_cast<std::string>(
                                         find_number_of_bytes_required(text.code_units.maximum)));
                    Si::append(code, " + ");
                    Si::append(code, library);
                    Si::append(code, ".to_utf8(");
                    Si::append(code, value);
                    Si::append(code, ").byteLength)");
                },
                [&](std::unique_ptr<types::structure> const &root)
                {
                    Si::append(code, "(0");
                    for (types::structure::element const &e : root->elements)
                    {
                        Si::append(code, " + ");
                        std::string element_value;
                        element_value.append(value.begin(), value.size());
                        element_value += ".";
                        element_value += e.name;
                        generate_size_of_value(code, indentation, e.what,
                                               Si::make_memory_range(element_value), library);
                    }
                    Si::append(code, ")");
                });
        }

        template <class CharSink>
        void generate_serialization(CharSink &&code, indentation_level indentation,
                                    types::type const &type, Si::memory_range const value,
                                    Si::memory_range const destination,
                                    Si::memory_range const offset, Si::memory_range const library)
        {
            Si::visit<void>(
                type,
                [&](types::integer const range)
                {
                    switch (find_number_of_bytes_required(range.maximum))
                    {
                    case 1:
                        start_line(code, indentation, "new DataView(", destination, ").setUint8(",
                                   offset, ", ", value, ");\n");
                        break;

                    case 2:
                        start_line(code, indentation, "new DataView(", destination, ").setUint16(",
                                   offset, ", ", value, ");\n");
                        break;

                    case 4:
                        start_line(code, indentation, "new DataView(", destination, ").setUint32(",
                                   offset, ", ", value, ");\n");
                        break;

                    case 8:
                        start_line(code, indentation, "new DataView(", destination, ").setUint32(",
                                   offset, ", ", value, ".high);\n");
                        start_line(code, indentation, "new DataView(", destination, ").setUint32(",
                                   offset, " + 4, ", value, ".low);\n");
                        break;

                    default:
                        SILICIUM_UNREACHABLE();
                    }
                },
                [&](std::unique_ptr<types::variant> const &)
                {
                    throw std::logic_error("todo");
                },
                [&](std::unique_ptr<types::tuple> const &root)
                {
                    if (root->elements.empty())
                    {
                        return;
                    }
                    start_line(code, indentation, "(function ()\n");
                    block(code, indentation,
                          [&](indentation_level const in_function)
                          {
                              start_line(code, in_function, "var offset = ", offset, ";\n");
                              for (size_t i = 0, c = root->elements.size(); i < c; ++i)
                              {
                                  std::string element_value;
                                  element_value.append(value.begin(), value.size());
                                  element_value += "[";
                                  element_value += boost::lexical_cast<std::string>(i);
                                  element_value += "]";
                                  generate_serialization(code, indentation, root->elements[i],
                                                         Si::make_memory_range(element_value),
                                                         destination,
                                                         Si::make_c_str_range("offset"), library);
                                  start_line(code, in_function, "offset += ");
                                  generate_size_of_value(code, in_function, root->elements[i],
                                                         Si::make_memory_range(element_value),
                                                         library);
                                  Si::append(code, ";\n");
                              }
                          },
                          ")();\n");
                },
                [&](std::unique_ptr<types::vector> const &)
                {
                    throw std::logic_error("todo");
                },
                [&](types::utf8 const text)
                {
                    std::string length(value.begin(), value.end());
                    length += ".length";
                    generate_serialization(code, indentation, text.code_units,
                                           Si::make_memory_range(length), destination, offset,
                                           library);
                    std::string content_offset = "(";
                    content_offset.append(boost::lexical_cast<std::string>(
                        find_number_of_bytes_required(text.code_units.maximum)));
                    content_offset.append(" + ");
                    content_offset.append(offset.begin(), offset.end());
                    content_offset.append(")");
                    start_line(code, indentation, "new Uint8Array(", destination,
                               ").set(new Uint8Array(", library, ".to_utf8(", value, ")), ",
                               content_offset, ");\n");
                },
                [&](std::unique_ptr<types::structure> const &root)
                {
                    if (root->elements.empty())
                    {
                        return;
                    }
                    start_line(code, indentation, "(function ()\n");
                    block(code, indentation,
                          [&](indentation_level const in_function)
                          {
                              start_line(code, in_function, "var offset = ", offset, ";\n");
                              for (types::structure::element const &e : root->elements)
                              {
                                  std::string element_value;
                                  element_value.append(value.begin(), value.size());
                                  element_value += ".";
                                  element_value += e.name;
                                  generate_serialization(code, indentation, e.what,
                                                         Si::make_memory_range(element_value),
                                                         destination,
                                                         Si::make_c_str_range("offset"), library);
                                  start_line(code, in_function, "offset += ");
                                  generate_size_of_value(code, in_function, e.what,
                                                         Si::make_memory_range(element_value),
                                                         library);
                                  Si::append(code, ";\n");
                              }
                          },
                          ")();\n");
                });
        }

        template <class CharSink>
        void generate_request_parsing(CharSink &&code, indentation_level indentation,
                                      types::interface_definition const &interface,
                                      Si::memory_range const common_library)
        {
            start_line(code, indentation, "var request_id_parser = ", common_library,
                       ".parse_int_8(0, 0, 0xffffffff, 0xffffffff);\n");
            start_line(code, indentation, "state = function (input)\n");
            block(
                code, indentation,
                [&](indentation_level const in_state)
                {
                    start_line(code, in_state, "var status = request_id_parser(input);\n");
                    start_line(code, in_state, "if (status === undefined)\n");
                    block(code, in_state,
                          [&](indentation_level const in_if)
                          {
                              start_line(code, in_if, "return;\n");
                          },
                          "\n");
                    start_line(code, in_state, "var request_id = status;\n");
                    start_line(code, in_state, "var method_name_parser = ");
                    generate_parser_creation(code, in_state, types::utf8{types::integer{0, 255}},
                                             common_library);
                    Si::append(code, ";\n");
                    start_line(code, in_state, "state = function (input)\n");
                    block(
                        code, in_state,
                        [&](indentation_level const in_if)
                        {
                            start_line(code, in_if, "var status = method_name_parser(input);\n");
                            start_line(code, in_if, "if (status === undefined)\n");
                            block(code, in_if,
                                  [&](indentation_level const in_if_2)
                                  {
                                      start_line(code, in_if_2, "return;\n");
                                  },
                                  "\n");
                            for (auto const &method : interface.methods)
                            {
                                start_line(code, in_if, "if (status === \"", method.first, "\")\n");
                                block(
                                    code, in_if,
                                    [&](indentation_level const in_if_2)
                                    {
                                        {
                                            types::tuple parameters;
                                            for (types::parameter const &parameter :
                                                 method.second.parameters)
                                            {
                                                parameters.elements.emplace_back(
                                                    clone(parameter.type_));
                                            }
                                            start_line(code, in_if_2, "var parameters = ");
                                            types::type const parameters_erased =
                                                Si::to_unique(std::move(parameters));
                                            generate_parser_creation(
                                                code, in_if_2, parameters_erased, common_library);
                                            Si::append(code, ";\n");
                                        }
                                        start_line(code, in_if_2, "state = function (input)\n");
                                        block(
                                            code, in_if_2,
                                            [&](indentation_level const in_function)
                                            {
                                                start_line(code, in_function, "var status = "
                                                                              "parameters(input);"
                                                                              "\n");
                                                start_line(code, in_function,
                                                           "if (status === undefined) "
                                                           "{ return undefined; }\n");
                                                start_line(code, in_function,
                                                           "state = initial_state;\n");
                                                start_line(code, in_function,
                                                           "server_implementation.", method.first,
                                                           "(");
                                                auto comma = warpcoil::make_comma_separator(
                                                    Si::ref_sink(code));
                                                for (size_t i = 0;
                                                     i < method.second.parameters.size(); ++i)
                                                {
                                                    comma.add_element();
                                                    Si::append(code, "status[");
                                                    Si::append(code,
                                                               boost::lexical_cast<std::string>(i));
                                                    Si::append(code, "]");
                                                }
                                                comma.add_element();
                                                Si::append(code, "function (result)\n");
                                                block(
                                                    code, in_function,
                                                    [&](indentation_level const in_callback)
                                                    {
                                                        start_line(code, in_callback,
                                                                   "var response_size "
                                                                   "= 1 + 8 + ");
                                                        generate_size_of_value(
                                                            code, in_callback, method.second.result,
                                                            Si::make_c_str_range("result"),
                                                            common_library);
                                                        Si::append(code, ";\n");
                                                        start_line(code, in_callback,
                                                                   "var "
                                                                   "response_buffer = "
                                                                   "new "
                                                                   "ArrayBuffer("
                                                                   "response_size);"
                                                                   "\n");
                                                        generate_serialization(
                                                            code, in_callback,
                                                            types::integer{0, 255},
                                                            Si::make_c_str_range("1"),
                                                            Si::make_c_str_range("response_"
                                                                                "buffer"),
                                                            Si::make_c_str_range("0"),
                                                            common_library);
                                                        generate_serialization(
                                                            code, in_callback,
                                                            types::integer{0, 0xffffffffffffffffu},
                                                            Si::make_c_str_range("request_id"),
                                                            Si::make_c_str_range("response_"
                                                                                "buffer"),
                                                            Si::make_c_str_range("1"),
                                                            common_library);
                                                        generate_serialization(
                                                            code, in_callback, method.second.result,
                                                            Si::make_c_str_range("result"),
                                                            Si::make_c_str_range("response_"
                                                                                "buffer"),
                                                            Si::make_c_str_range("(1 + 8)"),
                                                            common_library);
                                                        start_line(code, in_callback,
                                                                   "send_bytes("
                                                                   "response_buffer);"
                                                                   "\n");
                                                    },
                                                    "");
                                                Si::append(code, ");\n");
                                            },
                                            ";\n");
                                        start_line(code, in_if_2, "return;\n");
                                    },
                                    "\n");
                            }
                            start_line(code, in_if, "throw new Error(\"unknown "
                                                    "method: \" + status);\n");
                        },
                        ";\n");
                },
                ";\n");
        }

        template <class CharSink>
        void generate_input_receiver(CharSink &&code, indentation_level indentation,
                                     types::interface_definition const &interface,
                                     Si::memory_range const common_library)
        {
            start_line(code, indentation, "function (pending_requests, "
                                          "server_implementation, "
                                          "send_bytes)\n");
            block(code, indentation,
                  [&](indentation_level const in_function)
                  {
                      start_line(code, in_function, "var state;\n");
                      start_line(code, in_function, "var initial_state = function (input)\n");
                      block(code, in_function,
                            [&](indentation_level const in_first_state)
                            {
                                start_line(code, in_first_state, "if (input === 0)\n");
                                block(code, in_first_state,
                                      [&](indentation_level const in_if)
                                      {
                                          generate_request_parsing(code, in_if, interface,
                                                                   common_library);
                                      },
                                      "\n");
                                start_line(code, in_first_state, "else if (input === 1)\n");
                                block(code, in_first_state,
                                      [&](indentation_level const in_if)
                                      {
                                          generate_response_parsing(code, in_if, common_library);
                                      },
                                      "\n");
                                start_line(code, in_first_state, "else\n");
                                block(code, in_first_state,
                                      [&](indentation_level const in_else)
                                      {
                                          start_line(code, in_else, "throw new Error(\"unknown "
                                                                    "message type\");\n");
                                      },
                                      "\n");
                            },
                            ";\n");
                      start_line(code, in_function, "state = initial_state;\n");
                      start_line(code, in_function, "return function (input)\n");
                      block(code, in_function,
                            [&](indentation_level const in_parse)
                            {
                                start_line(code, in_parse, common_library,
                                           ".assert(undefined === state(input));\n");
                            },
                            ";\n");
                  },
                  "");
        }

        template <class CharSink>
        void generate_client(CharSink &&code, indentation_level indentation,
                             types::interface_definition const &definition,
                             Si::memory_range const library)
        {
            start_line(code, indentation, "function (pending_requests, send_bytes)\n");
            block(
                code, indentation,
                [&](indentation_level const in_function)
                {
                    start_line(code, in_function, "var next_request_id = 0;\n");
                    start_line(code, in_function, "return ");
                    continuation_block(
                        code, in_function,
                        [&](indentation_level const in_result)
                        {
                            for (auto i = definition.methods.begin(); i != definition.methods.end();
                                 ++i)
                            {
                                auto const &method = *i;
                                start_line(code, in_result, method.first, ": function (");
                                for (types::parameter const &parameter : method.second.parameters)
                                {
                                    Si::append(code, parameter.name);
                                    Si::append(code, ", ");
                                }
                                Si::append(code, "on_result)\n");
                                block(
                                    code, in_result,
                                    [&](indentation_level const in_method)
                                    {
                                        start_line(
                                            code, in_method, "var request_size = 1 + 8 + 1 + ",
                                            boost::lexical_cast<std::string>(method.first.size()));
                                        for (types::parameter const &parameter :
                                             method.second.parameters)
                                        {
                                            Si::append(code, " + ");
                                            generate_size_of_value(
                                                code, in_method, parameter.type_,
                                                Si::make_memory_range(parameter.name), library);
                                        }
                                        Si::append(code, ";\n");
                                        start_line(code, in_method, "var request_buffer = new "
                                                                    "ArrayBuffer(request_size);"
                                                                    "\n");
                                        generate_serialization(
                                            code, in_method, types::integer{0, 255},
                                            Si::make_c_str_range("0"),
                                            Si::make_c_str_range("request_buffer"),
                                            Si::make_c_str_range("0"), library);
                                        start_line(code, in_method, "var request_id = {high: 0, "
                                                                    "low: next_request_id};\n");
                                        // TODO: support more than 32 bit
                                        // request IDs
                                        start_line(code, in_method, "++next_request_id;\n");
                                        generate_serialization(
                                            code, in_method, types::integer{0, 0xffffffffffffffffu},
                                            Si::make_c_str_range("request_id"),
                                            Si::make_c_str_range("request_buffer"),
                                            Si::make_c_str_range("1"), library);
                                        start_line(code, in_method, "var write_pointer = 1 + 8;\n");
                                        {
                                            std::string method_name_literal = "\"";
                                            method_name_literal += method.first;
                                            method_name_literal += "\"";
                                            types::type const name_type =
                                                types::utf8{types::integer{0, 255}};
                                            generate_serialization(
                                                code, in_method, name_type,
                                                Si::make_memory_range(method_name_literal),
                                                Si::make_c_str_range("request_buffer"),
                                                Si::make_c_str_range("write_pointer"), library);
                                            if (!method.second.parameters.empty())
                                            {
                                                start_line(code, in_method, "write_pointer += ");
                                                generate_size_of_value(
                                                    code, in_method, name_type,
                                                    Si::make_memory_range(method_name_literal),
                                                    library);
                                                Si::append(code, ";\n");
                                            }
                                        }
                                        for (size_t k = 0; k < method.second.parameters.size(); ++k)
                                        {
                                            types::parameter const &parameter =
                                                method.second.parameters[k];
                                            generate_serialization(
                                                code, in_method, parameter.type_,
                                                Si::make_memory_range(parameter.name),
                                                Si::make_c_str_range("request_buffer"),
                                                Si::make_c_str_range("write_pointer"), library);
                                            if (k != (method.second.parameters.size() - 1))
                                            {
                                                start_line(code, in_method, "write_pointer += ");
                                                generate_size_of_value(
                                                    code, in_method, parameter.type_,
                                                    Si::make_memory_range(parameter.name), library);
                                                Si::append(code, ";\n");
                                            }
                                        }
                                        start_line(code, in_method,
                                                   "send_bytes(request_buffer);\n");
                                        start_line(code, in_method, "var key = "
                                                                    "JSON.stringify(request_id);"
                                                                    "\n");
                                        start_line(code, in_method, "pending_requests[key] = "
                                                                    "{argument_parser: ");
                                        generate_parser_creation(code, in_method,
                                                                 method.second.result, library);
                                        Si::append(code, ", on_result: on_result};\n");
                                    },
                                    "");
                                if (std::next(i, 1) != definition.methods.end())
                                {
                                    Si::append(code, ",");
                                }
                                Si::append(code, "\n");
                            }
                        },
                        ";\n");
                },
                "");
        }

        template <class CharSink>
        void generate_common_library(CharSink &&code, indentation_level indentation)
        {
            Si::append(code, "{\n");
            {
                indentation_level const in_object = indentation.deeper();
                start_line(code, in_object, "to_utf8: function (string)\n");
                {
                    block(code, in_object,
                          [&](indentation_level const in_function)
                          {
                              start_line(code, in_function, "var buffer = new "
                                                            "ArrayBuffer(string.length);\n");
                              start_line(code, in_function, "var view = new DataView(buffer);\n");
                              start_line(code, in_function, "for (var i = 0, c "
                                                            "= string.length; "
                                                            "i < c; ++i)\n");
                              block(code, in_function,
                                    [&](indentation_level const in_for)
                                    {
                                        // TODO: actual UTF-8 encoder
                                        start_line(code, in_for, "view.setUint8(i, "
                                                                 "string.charCodeAt(i));\n");
                                    },
                                    "\n");
                              start_line(code, in_function, "return buffer;\n");
                          },
                          ",\n");
                }

                start_line(code, in_object, "parse_int_1: ");
                warpcoil::javascript::generate_integer_parser(code, in_object, 1);
                Si::append(code, ",\n");

                start_line(code, in_object, "parse_int_2: ");
                warpcoil::javascript::generate_integer_parser(code, in_object, 2);
                Si::append(code, ",\n");

                start_line(code, in_object, "parse_int_4: ");
                warpcoil::javascript::generate_integer_parser(code, in_object, 4);
                Si::append(code, ",\n");

                start_line(code, in_object, "parse_int_8: ");
                warpcoil::javascript::generate_integer_parser(code, in_object, 8);
                Si::append(code, ",\n");

                start_line(code, in_object, "assert: ");
                Si::append(code, "function (x) { if (!(x)) { throw new "
                                 "Error(\"assertion failed\"); } },\n");

                start_line(code, in_object, "assert_eq: ");
                Si::append(code, "function (expected, got) { if (expected !== "
                                 "got) { throw new Error(\"Expected \" + "
                                 "expected + \", got \" + got); } },\n");

                start_line(code, in_object, "require_type: ");
                Si::append(code, "function (type, value) { "
                                 "assert(typeof(value) === type); }\n");
            }
            start_line(code, indentation, "}");
        }
    }
}