#include <cfloat>
#include <cstdlib>
#include <sys/time.h>

#include <boost/math/special_functions/next.hpp>
#include <boost/random.hpp>

#include <limits>
#include <omp.h>
#ifdef __INTEL_COMPILER
#include <immintrin.h>
#endif

#include "caffe/common.hpp"
#include "caffe/util/math_functions_intel.hpp"
#include "caffe/layers/conv_relu_pool_lrn_layer.hpp"
#include "caffe/util/cpu_info.hpp"
#include "caffe/util/sconv.hpp"

extern unsigned long long conv_cycles_of_this_batch[1024*16], transpose_cycle, pool_cycle;

int flop_cnt = 0;

static const double DEFAULT_CPU_FREQ = 3.33e9;
double get_cpu_freq()
{
  static double freq = DBL_MAX;
  if (DBL_MAX == freq) {
    volatile double a = rand()%1024, b = rand()%1024;
    struct timeval tv1, tv2;
    gettimeofday(&tv1, NULL);
    unsigned long long t1 = __rdtsc();
    for (size_t i = 0; i < 1024L*1024; i++) {
      a += a*b + b/a;
    }
    unsigned long long dt = __rdtsc() - t1;
    gettimeofday(&tv2, NULL);
    freq = dt/((tv2.tv_sec - tv1.tv_sec) + (tv2.tv_usec - tv1.tv_usec)/1.e6);
  }

  return freq;
}

namespace caffe {

static inline void transpose8_ps(__m256 &row0, __m256 &row1, __m256 &row2, __m256 &row3, __m256 &row4, __m256 &row5, __m256 &row6, __m256 &row7) {
  __m256 __t0, __t1, __t2, __t3, __t4, __t5, __t6, __t7;
  __m256 __tt0, __tt1, __tt2, __tt3, __tt4, __tt5, __tt6, __tt7;
  __t0 = _mm256_unpacklo_ps(row0, row1);
  __t1 = _mm256_unpackhi_ps(row0, row1);
  __t2 = _mm256_unpacklo_ps(row2, row3);
  __t3 = _mm256_unpackhi_ps(row2, row3);
  __t4 = _mm256_unpacklo_ps(row4, row5);
  __t5 = _mm256_unpackhi_ps(row4, row5);
  __t6 = _mm256_unpacklo_ps(row6, row7);
  __t7 = _mm256_unpackhi_ps(row6, row7);
  __tt0 = _mm256_shuffle_ps(__t0,__t2,_MM_SHUFFLE(1,0,1,0));
  __tt1 = _mm256_shuffle_ps(__t0,__t2,_MM_SHUFFLE(3,2,3,2));
  __tt2 = _mm256_shuffle_ps(__t1,__t3,_MM_SHUFFLE(1,0,1,0));
  __tt3 = _mm256_shuffle_ps(__t1,__t3,_MM_SHUFFLE(3,2,3,2));
  __tt4 = _mm256_shuffle_ps(__t4,__t6,_MM_SHUFFLE(1,0,1,0));
  __tt5 = _mm256_shuffle_ps(__t4,__t6,_MM_SHUFFLE(3,2,3,2));
  __tt6 = _mm256_shuffle_ps(__t5,__t7,_MM_SHUFFLE(1,0,1,0));
  __tt7 = _mm256_shuffle_ps(__t5,__t7,_MM_SHUFFLE(3,2,3,2));
  row0 = _mm256_permute2f128_ps(__tt0, __tt4, 0x20);
  row1 = _mm256_permute2f128_ps(__tt1, __tt5, 0x20);
  row2 = _mm256_permute2f128_ps(__tt2, __tt6, 0x20);
  row3 = _mm256_permute2f128_ps(__tt3, __tt7, 0x20);
  row4 = _mm256_permute2f128_ps(__tt0, __tt4, 0x31);
  row5 = _mm256_permute2f128_ps(__tt1, __tt5, 0x31);
  row6 = _mm256_permute2f128_ps(__tt2, __tt6, 0x31);
  row7 = _mm256_permute2f128_ps(__tt3, __tt7, 0x31);
}

// JSP: AlexNet each group of conv2
// Input: 48 x 27 x 27 => 6 KB per channel, 273 KB total
// Output: 128 x 27 x 27 => 6 KB per channel, 729 KB total
// Weight: 128 x 48 x 5 x 5 => 0.2 KB per channel pair, 9 KB per output channel, 25 KB per input channel, 1.2 MB total
//         No matter what we do, there's no reuse on weight across different channels (only reuse is within a channel pair)
// FLOPS: 2 x 128 x 48 x 27 x 27 x 5 x 5 = 224 MFLOPS

/**
 * Direct sparse convolution optimized for 2nd layer of AlexNet fused with bias and pooling layer
 */
static void __attribute__((noinline)) sconv2_fused(
    // input features
    const float *input,
    // weights
    const int *rowptr, const int *colidx, const float *values,
    // bias (for the case when bias is fused with convolution)
    const float *bias,
    // pooling (for the case when pooling is fused with convolution)
    float *pool_top, int *mask,
    // output features
    float *output, int oc_begin, int oc_end,
    int nthreads_per_group, int tid_in_group, int in_channels)
{
  static const int OC_BLOCK = 8;

  int WIDTH = 27;
  int WOUT = 27;
  int PAD = 2;

  for (int oc = oc_begin; oc < oc_end; ++oc) {
    unsigned long long t = __rdtsc();

    int out_channel_offset = tid_in_group*OC_BLOCK + oc%OC_BLOCK;

    int j;
    int jbegin = rowptr[oc];
    int jend = rowptr[oc + 1];
    int off;

#ifdef __AVX512F__
    __m512 sum[2][14];
    __m512 w_v;
    __m512 bias_v = _mm512_set1_ps(bias[oc]);

    // upper half
    int hbegin = 0, hend = 14;
#pragma unroll(14)
    for (int h = hbegin; h < hend; ++h) {
      sum[0][h - hbegin] = bias_v;
      sum[1][h - hbegin] = bias_v;
    }

    for (j = jbegin; j < jend; ++j) {
      w_v = _mm512_set1_ps(values[j]);
      off = colidx[j];

#pragma unroll(14)
      for (int h = hbegin; h < hend; ++h) {
        sum[0][h - hbegin] = _mm512_fmadd_ps(w_v, _mm512_loadu_ps(input + off + h*(WIDTH + PAD)), sum[0][h - hbegin]);
        sum[1][h - hbegin] = _mm512_fmadd_ps(w_v, _mm512_loadu_ps(input + off + h*(WIDTH + PAD) + 16), sum[1][h - hbegin]);
      }
    }

#pragma unroll(14)
    for (int h = hbegin; h < hend; ++h) {
      _mm512_store_ps(output + (out_channel_offset*WOUT + h)*32, sum[0][h - hbegin]);
      _mm512_store_ps(output + (out_channel_offset*WOUT + h)*32 + 16, sum[1][h - hbegin]);
    }

    // lower half
    hbegin = 14; hend = 27;

#pragma unroll(13)
    for (int h = hbegin; h < hend; ++h) {
      sum[0][h - hbegin] = bias_v;
      sum[1][h - hbegin] = bias_v;
    }

    for (j = jbegin; j < jend; ++j) {
      w_v = _mm512_set1_ps(values[j]);
      off = colidx[j];

#pragma unroll(13)
      for (int h = hbegin; h < hend; ++h) {
        sum[0][h - hbegin] = _mm512_fmadd_ps(w_v, _mm512_loadu_ps(input + off + h*(WIDTH + PAD)), sum[0][h - hbegin]);
        sum[1][h - hbegin] = _mm512_fmadd_ps(w_v, _mm512_loadu_ps(input + off + h*(WIDTH + PAD) + 16), sum[1][h - hbegin]);
      }
    }

#pragma unroll(13)
    for (int h = hbegin; h < hend; ++h) {
      _mm512_store_ps(output + (out_channel_offset*WOUT + h)*32, sum[0][h - hbegin]);
      _mm512_store_ps(output + (out_channel_offset*WOUT + h)*32 + 16, sum[1][h - hbegin]);
    }
#elif defined(__AVX2__)
    __m256 sum[(WOUT + 3)/4][2]; // [7][2]
    __m256 w_v;
    __m256 bias_v = _mm256_set1_ps(bias[oc]);

    // (0, 0) block

#undef MY_FMADD
#define MY_FMADD(HBEGIN, WBEGIN) \
    sum[0][0] = bias_v; \
    sum[0][1] = bias_v; \
    sum[1][0] = bias_v; \
    sum[1][1] = bias_v; \
    sum[2][0] = bias_v; \
    sum[2][1] = bias_v; \
    sum[3][0] = bias_v; \
    sum[3][1] = bias_v; \
    sum[4][0] = bias_v; \
    sum[4][1] = bias_v; \
    sum[5][0] = bias_v; \
    sum[5][1] = bias_v; \
    sum[6][0] = bias_v; \
    sum[6][1] = bias_v; \
\
    for (j = jbegin; j < jend; ++j) { \
      w_v = _mm256_set1_ps(values[j]); \
      off = colidx[j]; \
\
      sum[0][0] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 0)*(WIDTH + PAD) + WBEGIN), sum[0][0]); \
      sum[0][1] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 0)*(WIDTH + PAD) + WBEGIN + 8), sum[0][1]); \
      sum[1][0] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 1)*(WIDTH + PAD) + WBEGIN), sum[1][0]); \
      sum[1][1] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 1)*(WIDTH + PAD) + WBEGIN + 8), sum[1][1]); \
      sum[2][0] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 2)*(WIDTH + PAD) + WBEGIN), sum[2][0]); \
      sum[2][1] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 2)*(WIDTH + PAD) + WBEGIN + 8), sum[2][1]); \
      sum[3][0] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 3)*(WIDTH + PAD) + WBEGIN), sum[3][0]); \
      sum[3][1] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 3)*(WIDTH + PAD) + WBEGIN + 8), sum[3][1]); \
      sum[4][0] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 4)*(WIDTH + PAD) + WBEGIN), sum[4][0]); \
      sum[4][1] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 4)*(WIDTH + PAD) + WBEGIN + 8), sum[4][1]); \
      sum[5][0] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 5)*(WIDTH + PAD) + WBEGIN), sum[5][0]); \
      sum[5][1] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 5)*(WIDTH + PAD) + WBEGIN + 8), sum[5][1]); \
      sum[6][0] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 6)*(WIDTH + PAD) + WBEGIN), sum[6][0]); \
      sum[6][1] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 6)*(WIDTH + PAD) + WBEGIN + 8), sum[6][1]); \
      assert(off + (HBEGIN + 6)*(WIDTH + PAD) + WBEGIN + 16 <= in_channels*(WIDTH + PAD)*(WIDTH + PAD) + PAD*(WIDTH + 2*PAD)); \
    } \
\
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 0)*32 + WBEGIN, sum[0][0]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 0)*32 + WBEGIN + 8, sum[0][1]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 1)*32 + WBEGIN, sum[1][0]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 1)*32 + WBEGIN + 8, sum[1][1]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 2)*32 + WBEGIN, sum[2][0]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 2)*32 + WBEGIN + 8, sum[2][1]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 3)*32 + WBEGIN, sum[3][0]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 3)*32 + WBEGIN + 8, sum[3][1]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 4)*32 + WBEGIN, sum[4][0]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 4)*32 + WBEGIN + 8, sum[4][1]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 5)*32 + WBEGIN, sum[5][0]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 5)*32 + WBEGIN + 8, sum[5][1]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 6)*32 + WBEGIN, sum[6][0]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 6)*32 + WBEGIN + 8, sum[6][1]);

    MY_FMADD(0, 0);
    MY_FMADD(0, 16);

    MY_FMADD(7, 0);
    MY_FMADD(7, 16);

    MY_FMADD(14, 0);
    MY_FMADD(14, 16);
#undef MY_FMADD

#undef MY_FMADD_REMAINDER
#define MY_FMADD_REMAINDER(HBEGIN, WBEGIN) \
    sum[0][0] = bias_v; \
    sum[0][1] = bias_v; \
    sum[1][0] = bias_v; \
    sum[1][1] = bias_v; \
    sum[2][0] = bias_v; \
    sum[2][1] = bias_v; \
    sum[3][0] = bias_v; \
    sum[3][1] = bias_v; \
    sum[4][0] = bias_v; \
    sum[4][1] = bias_v; \
    sum[5][0] = bias_v; \
    sum[5][1] = bias_v; \
\
    for (j = jbegin; j < jend; ++j) { \
      w_v = _mm256_set1_ps(values[j]); \
      off = colidx[j]; \
\
      sum[0][0] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 0)*(WIDTH + PAD) + WBEGIN), sum[0][0]); \
      sum[0][1] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 0)*(WIDTH + PAD) + WBEGIN + 8), sum[0][1]); \
      sum[1][0] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 1)*(WIDTH + PAD) + WBEGIN), sum[1][0]); \
      sum[1][1] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 1)*(WIDTH + PAD) + WBEGIN + 8), sum[1][1]); \
      sum[2][0] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 2)*(WIDTH + PAD) + WBEGIN), sum[2][0]); \
      sum[2][1] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 2)*(WIDTH + PAD) + WBEGIN + 8), sum[2][1]); \
      sum[3][0] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 3)*(WIDTH + PAD) + WBEGIN), sum[3][0]); \
      sum[3][1] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 3)*(WIDTH + PAD) + WBEGIN + 8), sum[3][1]); \
      sum[4][0] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 4)*(WIDTH + PAD) + WBEGIN), sum[4][0]); \
      sum[4][1] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 4)*(WIDTH + PAD) + WBEGIN + 8), sum[4][1]); \
      sum[5][0] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 5)*(WIDTH + PAD) + WBEGIN), sum[5][0]); \
      sum[5][1] = _mm256_fmadd_ps(w_v, _mm256_loadu_ps(input + off + (HBEGIN + 5)*(WIDTH + PAD) + WBEGIN + 8), sum[5][1]); \
    } \
\
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 0)*32 + WBEGIN, sum[0][0]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 0)*32 + WBEGIN + 8, sum[0][1]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 1)*32 + WBEGIN, sum[1][0]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 1)*32 + WBEGIN + 8, sum[1][1]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 2)*32 + WBEGIN, sum[2][0]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 2)*32 + WBEGIN + 8, sum[2][1]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 3)*32 + WBEGIN, sum[3][0]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 3)*32 + WBEGIN + 8, sum[3][1]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 4)*32 + WBEGIN, sum[4][0]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 4)*32 + WBEGIN + 8, sum[4][1]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 5)*32 + WBEGIN, sum[5][0]); \
    _mm256_store_ps(output + (out_channel_offset*WOUT + HBEGIN + 5)*32 + WBEGIN + 8, sum[5][1]);

    MY_FMADD_REMAINDER(21, 0);
    MY_FMADD_REMAINDER(21, 16);
#undef MY_FMADD_REMAINDER
#else
    // !__AVX2__ && !__AVX512__
    __m128 sum[2][7];
    __declspec(aligned(64)) int mask_temp[4] = { -1, -1, -1, 0 };
    __m128i mask_v = _mm_load_si128((__m128i *)mask_temp);

    for (int h = 0; h < 26; h += 2) {
#pragma unroll(7)
      for (int w = 0; w < 7; ++w) {
        sum[0][w] = _mm_set1_ps(bias[oc]);
        sum[1][w] = _mm_set1_ps(bias[oc]);
      }

      for (int j = rowptr[oc]; j < rowptr[oc + 1]; ++j) {
        __m128 w_v = _mm_set1_ps(values[j]);
        int off = colidx[j];

#pragma unroll(7)
        for (int w = 0; w < 7; ++w) {
          sum[0][w] = _mm_add_ps(_mm_mul_ps(w_v, _mm_loadu_ps(input + off + h*(WIDTH + PAD) + w*4)), sum[0][w]);
          sum[1][w] = _mm_add_ps(_mm_mul_ps(w_v, _mm_loadu_ps(input + off + (h + 1)*(WIDTH + PAD) + w*4)), sum[1][w]);
        }
      }

#pragma unroll(6)
      for (int w = 0; w < 6; ++w) {
        _mm_storeu_ps(output + (oc*WOUT + h)*WOUT + w*4, sum[0][w]);
        _mm_storeu_ps(output + (oc*WOUT + h + 1)*WOUT + w*4, sum[1][w]);
      }
      _mm_maskmoveu_si128(_mm_castps_si128(sum[0][6]), mask_v, (char *)(output + (oc*WOUT + h)*WOUT + 24));
      _mm_maskmoveu_si128(_mm_castps_si128(sum[1][6]), mask_v, (char *)(output + (oc*WOUT + h + 1)*WOUT + 24));
    }

    int h = 26;
#pragma unroll(7)
    for (int w = 0; w < 7; ++w) {
      sum[0][w] = _mm_set1_ps(bias[oc]);
    }

    for (int j = rowptr[oc]; j < rowptr[oc + 1]; ++j) {
      __m128 w_v = _mm_set1_ps(values[j]);
      int off = colidx[j];

#pragma unroll(7)
      for (int w = 0; w < 7; ++w) {
        sum[0][w] = _mm_add_ps(_mm_mul_ps(w_v, _mm_loadu_ps(input + off + h*(WIDTH + PAD) + w*4)), sum[0][w]);
      }
    }

#pragma unroll(6)
    for (int w = 0; w < 6; ++w) {
      _mm_storeu_ps(output + (oc*WOUT + h)*WOUT + w*4, sum[0][w]);
    }
    _mm_maskmoveu_si128(_mm_castps_si128(sum[0][6]), mask_v, (char *)(output + (oc*WOUT + h)*WOUT + 24));

//    for (int output_row = 0; output_row < WOUT; ++output_row) {
//      for (int output_col = 0; output_col < WOUT; ++output_col) {
//
//        const float *in = input + output_row * (WIDTH + PAD) + output_col;
//
//        float sum = bias[oc];
//        for (int j = rowptr[oc]; j < rowptr[oc + 1]; ++j) {
//          sum += values[j]*in[colidx[j]];
//        }
//
//        output[(oc*WOUT + output_row)*WOUT + output_col] = sum;
//      }
//    }
#endif

    conv_cycles_of_this_batch[omp_get_thread_num()*16] += __rdtsc() - t;

#if defined(__AVX2__) || defined(__AVX512__)
    if (oc%OC_BLOCK != OC_BLOCK - 1) continue;

    t = __rdtsc();

    int t_offset = OC_BLOCK*tid_in_group;

    // transpose to vectorize pooling layer over multiple channels
    for (int h = 0; h < WOUT; ++h) {
      for (int w = 0; w < WOUT/OC_BLOCK*OC_BLOCK; w += OC_BLOCK) {
        __m256 v0 = _mm256_load_ps(output + (t_offset*WOUT + h)*32 + w);
        __m256 v1 = _mm256_load_ps(output + ((t_offset + 1)*WOUT + h)*32 + w);
        __m256 v2 = _mm256_load_ps(output + ((t_offset + 2)*WOUT + h)*32 + w);
        __m256 v3 = _mm256_load_ps(output + ((t_offset + 3)*WOUT + h)*32 + w);
        __m256 v4 = _mm256_load_ps(output + ((t_offset + 4)*WOUT + h)*32 + w);
        __m256 v5 = _mm256_load_ps(output + ((t_offset + 5)*WOUT + h)*32 + w);
        __m256 v6 = _mm256_load_ps(output + ((t_offset + 6)*WOUT + h)*32 + w);
        __m256 v7 = _mm256_load_ps(output + ((t_offset + 7)*WOUT + h)*32 + w);

        transpose8_ps(v0, v1, v2, v3, v4, v5, v6, v7);

        _mm256_store_ps(output + (((nthreads_per_group + tid_in_group)*32 + h)*WOUT + w)*8, v0);
        _mm256_store_ps(output + (((nthreads_per_group + tid_in_group)*32 + h)*WOUT + (w + 1))*8, v1);
        _mm256_store_ps(output + (((nthreads_per_group + tid_in_group)*32 + h)*WOUT + (w + 2))*8, v2);
        _mm256_store_ps(output + (((nthreads_per_group + tid_in_group)*32 + h)*WOUT + (w + 3))*8, v3);
        _mm256_store_ps(output + (((nthreads_per_group + tid_in_group)*32 + h)*WOUT + (w + 4))*8, v4);
        _mm256_store_ps(output + (((nthreads_per_group + tid_in_group)*32 + h)*WOUT + (w + 5))*8, v5);
        _mm256_store_ps(output + (((nthreads_per_group + tid_in_group)*32 + h)*WOUT + (w + 6))*8, v6);
        _mm256_store_ps(output + (((nthreads_per_group + tid_in_group)*32 + h)*WOUT + (w + 7))*8, v7);
      }
      for (int w = WOUT/8*8; w < WOUT; ++w) {
        for (int i = 0; i < 8; ++i) {
          output[(((nthreads_per_group + tid_in_group)*32 + h)*WOUT + w)*8 + i] = output[((t_offset + i)*WOUT + h)*32 + w];
        }
      }
    }

    if (0 == omp_get_thread_num()) transpose_cycle += __rdtsc() - t;
    t = __rdtsc();

    const int STRIDE_POOL = 2;
    const int K_POOL = 3;
    const int POOLED_WIDTH = (WOUT - K_POOL + STRIDE_POOL - 1) / STRIDE_POOL + 1; // (27 - 3 + 1)/2 + 1 = 13

    const float *conv_top_data_cur = output + (nthreads_per_group + tid_in_group)*32*WOUT*8;
    float *pool_top_data_cur = pool_top + (oc - 7)*POOLED_WIDTH*POOLED_WIDTH;
    int *mask_cur = mask + (oc - 7)*POOLED_WIDTH*POOLED_WIDTH;

    __declspec(aligned(64)) float maximum[8];

    __declspec(aligned(64)) int identity[8] = { 0, 1, 2, 3, 4, 5, 6, 7 };
    __m256i identity_v = _mm256_load_si256((__m256i *)identity);

    for (int ph = 0; ph < POOLED_WIDTH; ++ph) {
      __declspec(aligned(64)) int mask[8];

      int hstart = ph * STRIDE_POOL;
      int hend = hstart + K_POOL;

      for (int pw = 0; pw < POOLED_WIDTH; ++pw) {
        int wstart = pw * STRIDE_POOL;
        __m256 maximum_v = _mm256_setzero_ps(); // JSP: using 0 instead of -FLT_MAX does ReLU for us.
        __m256 mask_v = _mm256_setzero_ps();
        __m256 cmp_v, in_v;

        int index = hstart * WOUT + wstart;
        in_v = _mm256_load_ps(conv_top_data_cur + index*8);
        cmp_v = _mm256_cmp_ps(in_v, maximum_v, _CMP_LE_OQ);
        maximum_v =  _mm256_blendv_ps(in_v, maximum_v, cmp_v);
        mask_v = _mm256_blendv_ps(
            _mm256_castsi256_ps(_mm256_add_epi32(_mm256_set1_epi32(index*8), identity_v)),
            mask_v,
            cmp_v);

        index = hstart * WOUT + wstart + 1;
        in_v = _mm256_load_ps(conv_top_data_cur + index*8);
        cmp_v = _mm256_cmp_ps(in_v, maximum_v, _CMP_LE_OQ);
        maximum_v =  _mm256_blendv_ps(in_v, maximum_v, cmp_v);
        mask_v = _mm256_blendv_ps(
            _mm256_castsi256_ps(_mm256_add_epi32(_mm256_set1_epi32(index*8), identity_v)),
            mask_v,
            cmp_v);

        index = hstart * WOUT + wstart + 2;
        in_v = _mm256_load_ps(conv_top_data_cur + index*8);
        cmp_v = _mm256_cmp_ps(in_v, maximum_v, _CMP_LE_OQ);
        maximum_v =  _mm256_blendv_ps(in_v, maximum_v, cmp_v);
        mask_v = _mm256_blendv_ps(
            _mm256_castsi256_ps(_mm256_add_epi32(_mm256_set1_epi32(index*8), identity_v)),
            mask_v,
            cmp_v);

        index = (hstart + 1) * WOUT + wstart;
        in_v = _mm256_load_ps(conv_top_data_cur + index*8);
        cmp_v = _mm256_cmp_ps(in_v, maximum_v, _CMP_LE_OQ);
        maximum_v =  _mm256_blendv_ps(in_v, maximum_v, cmp_v);
        mask_v = _mm256_blendv_ps(
            _mm256_castsi256_ps(_mm256_add_epi32(_mm256_set1_epi32(index*8), identity_v)),
            mask_v,
            cmp_v);

        index = (hstart + 1) * WOUT + wstart + 1;
        in_v = _mm256_load_ps(conv_top_data_cur + index*8);
        cmp_v = _mm256_cmp_ps(in_v, maximum_v, _CMP_LE_OQ);
        maximum_v =  _mm256_blendv_ps(in_v, maximum_v, cmp_v);
        mask_v = _mm256_blendv_ps(
            _mm256_castsi256_ps(_mm256_add_epi32(_mm256_set1_epi32(index*8), identity_v)),
            mask_v,
            cmp_v);

        index = (hstart + 1) * WOUT + wstart + 2;
        in_v = _mm256_load_ps(conv_top_data_cur + index*8);
        cmp_v = _mm256_cmp_ps(in_v, maximum_v, _CMP_LE_OQ);
        maximum_v =  _mm256_blendv_ps(in_v, maximum_v, cmp_v);
        mask_v = _mm256_blendv_ps(
            _mm256_castsi256_ps(_mm256_add_epi32(_mm256_set1_epi32(index*8), identity_v)),
            mask_v,
            cmp_v);

        index = (hstart + 2) * WOUT + wstart;
        in_v = _mm256_load_ps(conv_top_data_cur + index*8);
        cmp_v = _mm256_cmp_ps(in_v, maximum_v, _CMP_LE_OQ);
        maximum_v =  _mm256_blendv_ps(in_v, maximum_v, cmp_v);
        mask_v = _mm256_blendv_ps(
            _mm256_castsi256_ps(_mm256_add_epi32(_mm256_set1_epi32(index*8), identity_v)),
            mask_v,
            cmp_v);

        index = (hstart + 2) * WOUT + wstart + 1;
        in_v = _mm256_load_ps(conv_top_data_cur + index*8);
        cmp_v = _mm256_cmp_ps(in_v, maximum_v, _CMP_LE_OQ);
        maximum_v =  _mm256_blendv_ps(in_v, maximum_v, cmp_v);
        mask_v = _mm256_blendv_ps(
            _mm256_castsi256_ps(_mm256_add_epi32(_mm256_set1_epi32(index*8), identity_v)),
            mask_v,
            cmp_v);

        index = (hstart + 2) * WOUT + wstart + 2;
        in_v = _mm256_load_ps(conv_top_data_cur + index*8);
        cmp_v = _mm256_cmp_ps(in_v, maximum_v, _CMP_LE_OQ);
        maximum_v =  _mm256_blendv_ps(in_v, maximum_v, cmp_v);
        mask_v = _mm256_blendv_ps(
            _mm256_castsi256_ps(_mm256_add_epi32(_mm256_set1_epi32(index*8), identity_v)),
            mask_v,
            cmp_v);

        _mm256_store_ps(maximum, maximum_v);
        _mm256_store_ps((float *)mask, mask_v);

        const int pool_index = ph * POOLED_WIDTH + pw;
        for (int j = 0; j < 8; ++j) {
          pool_top_data_cur[pool_index + j*POOLED_WIDTH*POOLED_WIDTH] = maximum[j];
          mask_cur[pool_index + j*POOLED_WIDTH*POOLED_WIDTH] = mask[j];
        }
      }
    }

    if (0 == omp_get_thread_num()) pool_cycle += __rdtsc() - t;
#endif
  } // for each out channel
}

template<>
void caffe_cpu_sconv_fused_with_relu_and_pooling(
    // input features
    const float *input_padded, int in_channels,
    int height, int width,
    int pad_h, int pad_w,
    int stride_h, int stride_w,
    int dilation_h, int dilation_w,
    // weights
    const int *rowptr, const int *colidx, const float *values,
    int kernel_h, int kernel_w,
    // bias (for the case when bias is fused with convolution)
    const float *bias,
    // pooling (for the case when pooling is fused with convolution)
    float *pool_top, int *mask,
    // output features
    float *output,
    int out_channels,
    int ninputs)
{
  if (dilation_h == 1 == dilation_w == 1 &&
      height == 27 && width == 27 &&
      pad_h == 2 && pad_w == 2 &&
      stride_h == 1 && stride_w == 1 &&
      kernel_w == 5 && kernel_h == 5) {

    static const int OC_BLOCK = 8;

    int nthreads_in_group = cpu::OpenMpManager::getNumThreadsInGroup(ninputs);
    int nthread_groups = cpu::OpenMpManager::getNumThreadGroups(ninputs);
    int nthreads_per_group = (omp_get_num_threads() + nthread_groups - 1)/nthread_groups;
    int tid_in_group = cpu::OpenMpManager::getThreadNumInGroup(ninputs);

    int num_oc_blocks = (out_channels + OC_BLOCK - 1)/OC_BLOCK;
    int oc_block_begin, oc_block_end;
    cpu::OpenMpManager::getSimpleGroupedThreadPartition(
        &oc_block_begin, &oc_block_end, num_oc_blocks, ninputs);

    int oc_begin = std::min(oc_block_begin*OC_BLOCK, out_channels);
    int oc_end = std::min(oc_block_end*OC_BLOCK, out_channels);

    sconv2_fused(
      input_padded,
      rowptr, colidx, values,
      bias,
      pool_top, mask,
      output,
      oc_begin, oc_end,
      nthreads_per_group, tid_in_group, in_channels);
    return;
  }
  else {
    LOG(FATAL) << "Unsupported code path";
    assert(false);
  }
}

template <bool FUSE_RELU>
void caffe_cpu_sconv(
    // input features
    const float *input_padded, int in_channels,
    int height, int width,
    int pad_h, int pad_w,
    int stride_h, int stride_w,
    int dilation_h, int dilation_w,
    // weights
    const int *rowptr, const int *colidx, const float *values,
    int kernel_h, int kernel_w,
    const int **rowptr_blocked, const int **colidx_blocked, const float **values_blocked,
    int ncolblocks,
    // bias (for the case when bias is fused with convolution)
    const float *bias,
    // output features
    float *output,
    int out_channels,
    float *output_scratch,
    int ninputs)
{
  const int output_h = (height + 2 * pad_h -
      (dilation_h * (kernel_h - 1) + 1)) / stride_h + 1;
  const int output_w = (width + 2 * pad_w -
      (dilation_w * (kernel_w - 1) + 1)) / stride_w + 1;

  if (dilation_h != 1 || dilation_w != 1) {
    // fall through to the default path
  }
  else if (stride_h == 1 && stride_w == 1 && height == width && kernel_h == kernel_w && pad_h == pad_w) {
    int num_oc_blocks = (out_channels + OC_BLOCK - 1)/OC_BLOCK;
    int oc_block_begin, oc_block_end;
    cpu::OpenMpManager::getSimpleGroupedThreadPartition(
        &oc_block_begin, &oc_block_end, num_oc_blocks, ninputs);

    int oc_begin = std::min(oc_block_begin*OC_BLOCK, out_channels);
    int oc_end = std::min(oc_block_end*OC_BLOCK, out_channels);

    if (kernel_h == 2*pad_h + 1) { // matched padding
      if (kernel_h == 1) {
        // the following sizes are used by GoogLeNet
        if (height == 4) {
          sconv_unit_stride<4, 1, FUSE_RELU>(
              input_padded,
              rowptr_blocked, colidx_blocked, values_blocked, ncolblocks,
              bias,
              output, oc_begin, oc_end, output_scratch,
              in_channels, out_channels);
          return;
        }
        else if (height == 7) {
          sconv_unit_stride<7, 1, FUSE_RELU>(
              input_padded,
              rowptr_blocked, colidx_blocked, values_blocked, ncolblocks,
              bias,
              output, oc_begin, oc_end, output_scratch,
              in_channels, out_channels);
          return;
        }
        else if (height == 14) {
          sconv_unit_stride<14, 1, FUSE_RELU>(
              input_padded,
              rowptr_blocked, colidx_blocked, values_blocked, ncolblocks,
              bias,
              output, oc_begin, oc_end, output_scratch,
              in_channels, out_channels);
          return;
        }
        else if (height == 28) {
          sconv_unit_stride<28, 1, FUSE_RELU>(
              input_padded,
              rowptr_blocked, colidx_blocked, values_blocked, ncolblocks,
              bias,
              output, oc_begin, oc_end, output_scratch,
              in_channels, out_channels);
          return;
        }
        else if (height == 56) {
          sconv_unit_stride<56, 1, FUSE_RELU>(
              input_padded,
              rowptr_blocked, colidx_blocked, values_blocked, ncolblocks,
              bias,
              output, oc_begin, oc_end, output_scratch,
              in_channels, out_channels);
          return;
        }
      }
      else if (kernel_h == 3) {
        if (height == 12) {
          // overfeat
          sconv_unit_stride<12, 3, FUSE_RELU>(
              input_padded,
              rowptr_blocked, colidx_blocked, values_blocked, ncolblocks,
              bias,
              output, oc_begin, oc_end, output_scratch,
              in_channels, out_channels);
          return;
        }
        else if (height == 13) {
          // alexnet conv3-5
          sconv_unit_stride<13, 3, FUSE_RELU>(
              input_padded,
              rowptr_blocked, colidx_blocked, values_blocked, ncolblocks,
              bias,
              output, oc_begin, oc_end, output_scratch,
              in_channels, out_channels);
          return;
        }
        // the following sizes are used by GoogLeNet
        else if (height == 7) {
          sconv_unit_stride<7, 3, FUSE_RELU>(
              input_padded,
              rowptr_blocked, colidx_blocked, values_blocked, ncolblocks,
              bias,
              output, oc_begin, oc_end, output_scratch,
              in_channels, out_channels);
          return;
        }
        else if (height == 14) {
          sconv_unit_stride<14, 3, FUSE_RELU>(
              input_padded,
              rowptr_blocked, colidx_blocked, values_blocked, ncolblocks,
              bias,
              output, oc_begin, oc_end, output_scratch,
              in_channels, out_channels);
          return;
        }
        else if (height == 28) {
          sconv_unit_stride<28, 3, FUSE_RELU>(
              input_padded,
              rowptr_blocked, colidx_blocked, values_blocked, ncolblocks,
              bias,
              output, oc_begin, oc_end, output_scratch,
              in_channels, out_channels);
          return;
        }
        else if (height == 56) {
          sconv_unit_stride<56, 3, FUSE_RELU>(
              input_padded,
              rowptr_blocked, colidx_blocked, values_blocked, ncolblocks,
              bias,
              output, oc_begin, oc_end, output_scratch,
              in_channels, out_channels);
          return;
        }
        // OCR public
        else if (height == 3) {
          sconv_unit_stride<3, 3, FUSE_RELU>(
              input_padded,
              rowptr_blocked, colidx_blocked, values_blocked, ncolblocks,
              bias,
              output, oc_begin, oc_end, output_scratch,
              in_channels, out_channels);
          return;
        }
      }
      else if (kernel_h == 5) {
        // AlexNet conv2
        if (height == 27) {
          sconv_unit_stride<27, 5, FUSE_RELU>(
              input_padded,
              rowptr_blocked, colidx_blocked, values_blocked, ncolblocks,
              bias,
              output, oc_begin, oc_end, output_scratch,
              in_channels, out_channels);
          return;
        }
        // the following sizes are used by GoogLeNet
        else if (height == 7) {
          sconv_unit_stride<7, 5, FUSE_RELU>(
              input_padded,
              rowptr_blocked, colidx_blocked, values_blocked, ncolblocks,
              bias,
              output, oc_begin, oc_end, output_scratch,
              in_channels, out_channels);
          return;
        }
        else if (height == 14) {
          sconv_unit_stride<14, 5, FUSE_RELU>(
              input_padded,
              rowptr_blocked, colidx_blocked, values_blocked, ncolblocks,
              bias,
              output, oc_begin, oc_end, output_scratch,
              in_channels, out_channels);
          return;
        }
        else if (height == 28) {
          sconv_unit_stride<28, 5, FUSE_RELU>(
              input_padded,
              rowptr_blocked, colidx_blocked, values_blocked, ncolblocks,
              bias,
              output, oc_begin, oc_end, output_scratch,
              in_channels, out_channels);
          return;
        }
      }
    }
    else if (0 == pad_h) { // zero padding

    }
  }
  else if (height == 227 && width == 227 && pad_h == 0 && pad_w == 0 && stride_h == 4 && stride_w == 4 && kernel_w == 11 && kernel_h == 11) {
    // conv1 of AlexNet
    assert(!FUSE_RELU);

    int WIDTH = 227;
    int STRIDE = 4;
    int K = 11;
    int WOUT = (WIDTH - K)/STRIDE + 1; // 55
    const int JBLOCK = 128;
    const int HBLOCK = 8;
    const int WBLOCK = 9;

    __declspec(aligned(64)) float sum[WOUT*WOUT];

    for (int out_channel = 0; out_channel < out_channels; ++out_channel) {
      int jbegin = rowptr[out_channel];
      int jend = std::min(jbegin + JBLOCK, rowptr[out_channel + 1]);

      for (int hbegin = 0; hbegin < WOUT; hbegin += HBLOCK) {
        int hend = std::min(hbegin + HBLOCK, WOUT);

        for (int wbegin = 0; wbegin < WOUT; wbegin += WBLOCK) {
          int wend = std::min(wbegin + WBLOCK, WOUT);

          for (int k = 0; k < (hend - hbegin) * (wend - wbegin); ++k) {
            sum[k] = 0;
          }

          for (int j = jbegin; j < jend; ++j) {
            float c = values[j];
            int off = colidx[j];
            int k = 0;
            for (int h = hbegin; h < hend; ++h) {
              for (int w = wbegin; w < wend; ++w, ++k) {
                sum[k] += c*input_padded[off + (h*WIDTH + w)*STRIDE];
              }
            }
          }

          int k = 0;
          for (int h = hbegin; h < hend; ++h) {
            for (int w = wbegin; w < wend; ++w, ++k) {
              output[(out_channel*WOUT + h)*WOUT + w] = sum[k];
            }
          }
        }
      }
      jbegin += JBLOCK;

      for ( ; jbegin < rowptr[out_channel + 1]; jbegin += JBLOCK) {
        int jend = std::min(jbegin + JBLOCK, rowptr[out_channel + 1]);

        for (int hbegin = 0; hbegin < WOUT; hbegin += HBLOCK) {
          int hend = std::min(hbegin + HBLOCK, WOUT);

          for (int wbegin = 0; wbegin < WOUT; wbegin += WBLOCK) {
            int wend = std::min(wbegin + WBLOCK, WOUT);

            for (int k = 0; k < (hend - hbegin) * (wend - wbegin); ++k) {
              sum[k] = 0;
            }

            for (int j = jbegin; j < jend; ++j) {
              float c = values[j];
              int off = colidx[j];
              int k = 0;
              for (int h = hbegin; h < hend; ++h) {
                for (int w = wbegin; w < wend; ++w, ++k) {
                  sum[k] += c*input_padded[off + (h*WIDTH + w)*STRIDE];
                }
              }
            }

            int k = 0;
            for (int h = hbegin; h < hend; ++h) {
              for (int w = wbegin; w < wend; ++w, ++k) {
                output[(out_channel*WOUT + h)*WOUT + w] += sum[k];
              }
            }
          }
        }
      }

//            for (int j = rowptr[out_channel]; j < rowptr[out_channel + 1]; ++j) {
//              float c = values[j];
//              int off = colidx[j];
//              for (int h = 0; h < WOUT/2; ++h) {
//                for (int w = WOUT/2; w < WOUT; ++w) {
//                  sum[h*WOUT + w] += c*in_temp[off + (h*WIDTH + w)*STRIDE];
//                }
//              }
//            }
//
//            for (int h = 0; h < WOUT/2; ++h) {
//              for (int w = WOUT/2; w < WOUT; ++w) {
//                output[(out_channel*WOUT + h)*WOUT + w] = sum[h*WOUT + w];
//              }
//            }
//
//            for (int h = WOUT/2; h < WOUT; ++h) {
//              for (int w = 0; w < WOUT/2; ++w) {
//                sum[h*WOUT + w] = 0;
//              }
//            }
//
//            for (int j = rowptr[out_channel]; j < rowptr[out_channel + 1]; ++j) {
//              float c = values[j];
//              int off = colidx[j];
//              for (int h = WOUT/2; h < WOUT; ++h) {
//                for (int w = 0; w < WOUT/2; ++w) {
//                  sum[h*WOUT + w] += c*in_temp[off + (h*WIDTH + w)*STRIDE];
//                }
//              }
//            }
//
//            for (int h = WOUT/2; h < WOUT; ++h) {
//              for (int w = 0; w < WOUT/2; ++w) {
//                output[(out_channel*WOUT + h)*WOUT + w] = sum[h*WOUT + w];
//              }
//            }
//
//            for (int h = WOUT/2; h < WOUT; ++h) {
//              for (int w = WOUT/2; w < WOUT; ++w) {
//                sum[h*WOUT + w] = 0;
//              }
//            }
//
//            for (int j = rowptr[out_channel]; j < rowptr[out_channel + 1]; ++j) {
//              float c = values[j];
//              int off = colidx[j];
//              for (int h = WOUT/2; h < WOUT; ++h) {
//                for (int w = WOUT/2; w < WOUT; ++w) {
//                  sum[h*WOUT + w] += c*in_temp[off + (h*WIDTH + w)*STRIDE];
//                }
//              }
//            }
//
//            for (int h = WOUT/2; h < WOUT; ++h) {
//              for (int w = WOUT/2; w < WOUT; ++w) {
//                output[(out_channel*WOUT + h)*WOUT + w] = sum[h*WOUT + w];
//              }
//            }
    }
    return;
  }

  LOG(WARNING) <<
      "Inefficient code path: kernel " << kernel_w << "x" << kernel_h <<
      " image " << width << "x" << height <<
      " pad " << pad_w << "x" << pad_h <<
      " stride " << stride_w << "x" << stride_h <<
      " dilation " << dilation_w << "x" << dilation_h;

  caffe_cpu_sconv_default<FUSE_RELU>(
      // input features
      input_padded, in_channels,
      height, width,
      pad_h, pad_w,
      stride_h, stride_w,
      dilation_h, dilation_w,
      // weights
      rowptr, colidx, values,
      kernel_h, kernel_w,
      bias,
      // output features
      output, out_channels);
}

template
void caffe_cpu_sconv<false>(
    // input features
    const float *input_padded, int in_channels,
    int height, int width,
    int pad_h, int pad_w,
    int stride_h, int stride_w,
    int dilation_h, int dilation_w,
    // weights
    const int *rowptr, const int *colidx, const float *values,
    int kernel_h, int kernel_w,
    const int **rowptr_blocked, const int **colidx_blocked, const float **values_blocked,
    int ncolblocks,
    // bias (for the case when bias is fused with convolution)
    const float *bias,
    // output features
    float *output,
    int out_channels,
    float *output_scratch,
    int ninputs);

template
void caffe_cpu_sconv<true>(
    // input features
    const float *input_padded, int in_channels,
    int height, int width,
    int pad_h, int pad_w,
    int stride_h, int stride_w,
    int dilation_h, int dilation_w,
    // weights
    const int *rowptr, const int *colidx, const float *values,
    int kernel_h, int kernel_w,
    const int **rowptr_blocked, const int **colidx_blocked, const float **values_blocked,
    int ncolblocks,
    // bias (for the case when bias is fused with convolution)
    const float *bias,
    // output features
    float *output,
    int out_channels,
    float *output_scratch,
    int ninputs);

}  // namespace caffe


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2014.
// Modifications copyright (c) 2014, Oracle and/or its affiliates.

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_INTERSECTION_MULTI_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_INTERSECTION_MULTI_HPP

#include <boost/geometry/core/closure.hpp>
#include <boost/geometry/core/geometry_id.hpp>
#include <boost/geometry/core/is_areal.hpp>
#include <boost/geometry/core/point_order.hpp>
#include <boost/geometry/core/tags.hpp>
#include <boost/geometry/geometries/concepts/check.hpp>

// TODO: those headers probably may be removed
#include <boost/geometry/algorithms/detail/overlay/get_ring.hpp>
#include <boost/geometry/algorithms/detail/overlay/get_turns.hpp>
#include <boost/geometry/algorithms/detail/overlay/copy_segments.hpp>
#include <boost/geometry/algorithms/detail/overlay/copy_segment_point.hpp>
#include <boost/geometry/algorithms/detail/overlay/select_rings.hpp>
#include <boost/geometry/algorithms/detail/sections/range_by_section.hpp>
#include <boost/geometry/algorithms/detail/sections/sectionalize.hpp>

#include <boost/geometry/algorithms/detail/intersection/interface.hpp>

#include <boost/geometry/algorithms/covered_by.hpp>
#include <boost/geometry/algorithms/envelope.hpp>
#include <boost/geometry/algorithms/num_points.hpp>

// TODO: remove this after moving num_point from multi directory
#include <boost/geometry/multi/algorithms/num_points.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace intersection
{

template <typename PointOut>
struct intersection_multi_linestring_multi_linestring_point
{
    template
    <
        typename MultiLinestring1, typename MultiLinestring2,
        typename RobustPolicy,
        typename OutputIterator, typename Strategy
    >
    static inline OutputIterator apply(MultiLinestring1 const& ml1,
            MultiLinestring2 const& ml2,
            RobustPolicy const& robust_policy,
            OutputIterator out,
            Strategy const& strategy)
    {
        // Note, this loop is quadratic w.r.t. number of linestrings per input.
        // Future Enhancement: first do the sections of each, then intersect.
        for (typename boost::range_iterator
                <
                    MultiLinestring1 const
                >::type it1 = boost::begin(ml1);
            it1 != boost::end(ml1);
            ++it1)
        {
            for (typename boost::range_iterator
                    <
                        MultiLinestring2 const
                    >::type it2 = boost::begin(ml2);
                it2 != boost::end(ml2);
                ++it2)
            {
                out = intersection_linestring_linestring_point<PointOut>
                      ::apply(*it1, *it2, robust_policy, out, strategy);
            }
        }

        return out;
    }
};

template <typename PointOut>
struct intersection_linestring_multi_linestring_point
{
    template
    <
        typename Linestring, typename MultiLinestring,
        typename RobustPolicy,
        typename OutputIterator, typename Strategy
    >
    static inline OutputIterator apply(Linestring const& linestring,
            MultiLinestring const& ml,
            RobustPolicy const& robust_policy,
            OutputIterator out,
            Strategy const& strategy)
    {
        for (typename boost::range_iterator
                <
                    MultiLinestring const
                >::type it = boost::begin(ml);
            it != boost::end(ml);
            ++it)
        {
            out = intersection_linestring_linestring_point<PointOut>
                  ::apply(linestring, *it, robust_policy, out, strategy);
        }

        return out;
    }
};

// This loop is quite similar to the loop above, but beacuse the iterator
// is second (above) or first (below) argument, it is not trivial to merge them.
template
<
    bool ReverseAreal,
    typename LineStringOut,
    overlay_type OverlayType
>
struct intersection_of_multi_linestring_with_areal
{
    template
    <
        typename MultiLinestring, typename Areal,
        typename RobustPolicy,
        typename OutputIterator, typename Strategy
    >
    static inline OutputIterator apply(MultiLinestring const& ml, Areal const& areal,
            RobustPolicy const& robust_policy,
            OutputIterator out,
            Strategy const& strategy)
    {
        for (typename boost::range_iterator
                <
                    MultiLinestring const
                >::type it = boost::begin(ml);
            it != boost::end(ml);
            ++it)
        {
            out = intersection_of_linestring_with_areal
                <
                    ReverseAreal, LineStringOut, OverlayType
                >::apply(*it, areal, robust_policy, out, strategy);
        }

        return out;

    }
};

// This one calls the one above with reversed arguments
template
<
    bool ReverseAreal,
    typename LineStringOut,
    overlay_type OverlayType
>
struct intersection_of_areal_with_multi_linestring
{
    template
    <
        typename Areal, typename MultiLinestring,
        typename RobustPolicy,
        typename OutputIterator, typename Strategy
    >
    static inline OutputIterator apply(Areal const& areal, MultiLinestring const& ml,
            RobustPolicy const& robust_policy,
            OutputIterator out,
            Strategy const& strategy)
    {
        return intersection_of_multi_linestring_with_areal
            <
                ReverseAreal, LineStringOut, OverlayType
            >::apply(ml, areal, robust_policy, out, strategy);
    }
};

template <typename LinestringOut>
struct clip_multi_linestring
{
    template
    <
        typename MultiLinestring, typename Box,
        typename RobustPolicy,
        typename OutputIterator, typename Strategy
    >
    static inline OutputIterator apply(MultiLinestring const& multi_linestring,
            Box const& box,
            RobustPolicy const& robust_policy,
            OutputIterator out, Strategy const& )
    {
        typedef typename point_type<LinestringOut>::type point_type;
        strategy::intersection::liang_barsky<Box, point_type> lb_strategy;
        for (typename boost::range_iterator<MultiLinestring const>::type it
            = boost::begin(multi_linestring);
            it != boost::end(multi_linestring); ++it)
        {
            out = detail::intersection::clip_range_with_box
                <LinestringOut>(box, *it, robust_policy, out, lb_strategy);
        }
        return out;
    }
};

}} // namespace detail::intersection
#endif // DOXYGEN_NO_DETAIL

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

// Linear
template
<
    typename MultiLinestring1, typename MultiLinestring2,
    typename GeometryOut,
    overlay_type OverlayType,
    bool Reverse1, bool Reverse2, bool ReverseOut
>
struct intersection_insert
    <
        MultiLinestring1, MultiLinestring2,
        GeometryOut,
        OverlayType,
        Reverse1, Reverse2, ReverseOut,
        multi_linestring_tag, multi_linestring_tag, point_tag,
        false, false, false
    > : detail::intersection::intersection_multi_linestring_multi_linestring_point
            <
                GeometryOut
            >
{};

template
<
    typename Linestring, typename MultiLinestring,
    typename GeometryOut,
    overlay_type OverlayType,
    bool Reverse1, bool Reverse2, bool ReverseOut
>
struct intersection_insert
    <
        Linestring, MultiLinestring,
        GeometryOut,
        OverlayType,
        Reverse1, Reverse2, ReverseOut,
        linestring_tag, multi_linestring_tag, point_tag,
        false, false, false
    > : detail::intersection::intersection_linestring_multi_linestring_point
            <
                GeometryOut
            >
{};

template
<
    typename MultiLinestring, typename Box,
    typename GeometryOut,
    overlay_type OverlayType,
    bool Reverse1, bool Reverse2, bool ReverseOut
>
struct intersection_insert
    <
        MultiLinestring, Box,
        GeometryOut,
        OverlayType,
        Reverse1, Reverse2, ReverseOut,
        multi_linestring_tag, box_tag, linestring_tag,
        false, true, false
    > : detail::intersection::clip_multi_linestring
            <
                GeometryOut
            >
{};

template
<
    typename Linestring, typename MultiPolygon,
    typename GeometryOut,
    overlay_type OverlayType,
    bool ReverseLinestring, bool ReverseMultiPolygon, bool ReverseOut
>
struct intersection_insert
    <
        Linestring, MultiPolygon,
        GeometryOut,
        OverlayType,
        ReverseLinestring, ReverseMultiPolygon, ReverseOut,
        linestring_tag, multi_polygon_tag, linestring_tag,
        false, true, false
    > : detail::intersection::intersection_of_linestring_with_areal
            <
                ReverseMultiPolygon,
                GeometryOut,
                OverlayType
            >
{};

// Derives from areal/mls because runtime arguments are in that order.
// areal/mls reverses it itself to mls/areal
template
<
    typename Polygon, typename MultiLinestring,
    typename GeometryOut,
    overlay_type OverlayType,
    bool ReversePolygon, bool ReverseMultiLinestring, bool ReverseOut
>
struct intersection_insert
    <
        Polygon, MultiLinestring,
        GeometryOut,
        OverlayType,
        ReversePolygon, ReverseMultiLinestring, ReverseOut,
        polygon_tag, multi_linestring_tag, linestring_tag,
        true, false, false
    > : detail::intersection::intersection_of_areal_with_multi_linestring
            <
                ReversePolygon,
                GeometryOut,
                OverlayType
            >
{};

template
<
    typename MultiLinestring, typename Ring,
    typename GeometryOut,
    overlay_type OverlayType,
    bool ReverseMultiLinestring, bool ReverseRing, bool ReverseOut
>
struct intersection_insert
    <
        MultiLinestring, Ring,
        GeometryOut,
        OverlayType,
        ReverseMultiLinestring, ReverseRing, ReverseOut,
        multi_linestring_tag, ring_tag, linestring_tag,
        false, true, false
    > : detail::intersection::intersection_of_multi_linestring_with_areal
            <
                ReverseRing,
                GeometryOut,
                OverlayType
            >
{};

template
<
    typename MultiLinestring, typename Polygon,
    typename GeometryOut,
    overlay_type OverlayType,
    bool ReverseMultiLinestring, bool ReverseRing, bool ReverseOut
>
struct intersection_insert
    <
        MultiLinestring, Polygon,
        GeometryOut,
        OverlayType,
        ReverseMultiLinestring, ReverseRing, ReverseOut,
        multi_linestring_tag, polygon_tag, linestring_tag,
        false, true, false
    > : detail::intersection::intersection_of_multi_linestring_with_areal
            <
                ReverseRing,
                GeometryOut,
                OverlayType
            >
{};

template
<
    typename MultiLinestring, typename MultiPolygon,
    typename GeometryOut,
    overlay_type OverlayType,
    bool ReverseMultiLinestring, bool ReverseMultiPolygon, bool ReverseOut
>
struct intersection_insert
    <
        MultiLinestring, MultiPolygon,
        GeometryOut,
        OverlayType,
        ReverseMultiLinestring, ReverseMultiPolygon, ReverseOut,
        multi_linestring_tag, multi_polygon_tag, linestring_tag,
        false, true, false
    > : detail::intersection::intersection_of_multi_linestring_with_areal
            <
                ReverseMultiPolygon,
                GeometryOut,
                OverlayType
            >
{};

} // namespace dispatch
#endif

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_INTERSECTION_MULTI_HPP


/*
See the Zcash protocol specification for more information.
https://github.com/zcash/zips/blob/master/protocol/protocol.pdf
*/

#ifndef ZC_NOTE_ENCRYPTION_H_
#define ZC_NOTE_ENCRYPTION_H_

#include <boost/array.hpp>
#include "uint256.h"

#include "zerocash/Zerocash.h"

namespace libzcash {

#define NOTEENCRYPTION_AUTH_BYTES 16

template<size_t MLEN>
class NoteEncryption {
protected:
    enum { CLEN=MLEN+NOTEENCRYPTION_AUTH_BYTES };
    uint256 epk;
    uint256 esk;
    unsigned char nonce;
    uint256 hSig;

public:
    typedef boost::array<unsigned char, CLEN> Ciphertext;
    typedef boost::array<unsigned char, MLEN> Plaintext;

    NoteEncryption(uint256 hSig);

    // Gets the ephemeral public key
    uint256 get_epk() {
        return epk;
    }

    // Encrypts `message` with `pk_enc` and returns the ciphertext.
    // This can only be called twice for a given instantiation before
    // the nonce-space runs out.
    Ciphertext encrypt(const uint256 &pk_enc,
                       const Plaintext &message
                      );

    // Creates a NoteEncryption private key
    static uint256 generate_privkey(const uint256 &a_sk);

    // Creates a NoteEncryption public key from a private key
    static uint256 generate_pubkey(const uint256 &sk_enc);
};

template<size_t MLEN>
class NoteDecryption {
protected:
    enum { CLEN=MLEN+NOTEENCRYPTION_AUTH_BYTES };
    uint256 sk_enc;
    uint256 pk_enc;

public:
    typedef boost::array<unsigned char, CLEN> Ciphertext;
    typedef boost::array<unsigned char, MLEN> Plaintext;

    NoteDecryption(uint256 sk_enc);

    Plaintext decrypt(const Ciphertext &ciphertext,
                      const uint256 &epk,
                      const uint256 &hSig,
                      unsigned char nonce
                     ) const;
};

uint256 random_uint256();

}

typedef libzcash::NoteEncryption<ZC_V_SIZE + ZC_RHO_SIZE + ZC_R_SIZE + ZC_MEMO_SIZE> ZCNoteEncryption;
typedef libzcash::NoteDecryption<ZC_V_SIZE + ZC_RHO_SIZE + ZC_R_SIZE + ZC_MEMO_SIZE> ZCNoteDecryption;

#endif /* ZC_NOTE_ENCRYPTION_H_ */


#include "biggles/observation.hpp"
#include "biggles/detail/random.hpp"
#include "biggles/mh_moves/mh_moves.hpp"
#include "biggles/mh_moves/utility.hpp"
#include "biggles/simulate.hpp"
#include <boost/foreach.hpp>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <fstream>
#include <algorithm>
#include <string>
#include "biggles/tools/sundries.hpp"

// include this last to stop pre-processor macros breaking things
extern "C" {
#include <ccan/tap/tap.h>
}

using namespace biggles;

void print_obs(observation obs) {
    std::cout << "x=" << x(obs) << ", y= " << y(obs) << ", t= " << t(obs) << std::endl;
}

bool minimum_partition_test() {
    observation_collection cl_obs;
    cl_obs.insert(new_obs(1, 1, 0));
    cl_obs.insert(new_obs(1, 1, 1));
    boost::shared_ptr<track_collection> tracks(new track_collection());
    clutter_ptr clutter(new clutter_t(cl_obs.begin(), cl_obs.end()));
    partition_ptr_t original_part_ptr(
        new partition (tracks, clutter, expansion_from_observations(*tracks, *clutter)));

    bool done = false;
    for (int i = 0; i < 100; ++i) {
        float prob=0.f;
        partition_ptr_t target_part_ptr;
        done = mh_moves::birth(original_part_ptr, target_part_ptr, prob);
        if (done) break;
    }
    return done;
}

int test_pdr(const int total, const random_gen_paras& paras) {
    partition_ptr_t original_part_ptr(random_debug_partition(paras));

    capability_recorder_ptr cap_rec_ptr(new capability_recorder(
        original_part_ptr->first_time_stamp(), original_part_ptr->last_time_stamp(), original_part_ptr->tracks()));
    original_part_ptr->set_capability_recorder(cap_rec_ptr);

    test_t tests;
    BOOST_FOREACH (const std::string& msg,  move_pdr_test(original_part_ptr, "Birth", total, tests)) {
        diag("%s", msg.c_str());
    }
    BOOST_FOREACH (const test_t::value_type& item, tests) {
        ok(item.second, "%s", item.first.c_str());
    }
    return 0;
}

int test1(const int total, const random_gen_paras& paras) {
    observation_collection cl_obs;

    for (time_stamp i = 0; i < 3; ++i)
        cl_obs.insert(new_obs(1, 1, i));
    boost::shared_ptr<track_collection> tracks(new track_collection());
    clutter_ptr clutter(new clutter_t(cl_obs.begin(), cl_obs.end()));

    partition_ptr_t original_part_ptr(new partition(tracks, clutter, expansion_from_observations(*tracks, *clutter)));

    test_t tests;
    BOOST_FOREACH (const std::string& msg,  move_pdr_test(original_part_ptr, "Birth", total, tests)) {
        diag("%s", msg.c_str());
    }
    BOOST_FOREACH (const test_t::value_type& item, tests) {
        ok(item.second, "%s", item.first.c_str());
    }
    return 0;
}

int main(int argc, char** argv)
{
    general_paras gen_pars;
    gen_pars.total = 30000;
    gen_pars.seed = 0;
    gen_pars.rgp.lambda = 2.5;
    gen_pars.rgp.p_no = 0.15;
    gen_pars.rgp.p_yes = 0.15;
    gen_pars.rgp.p_tr = 0.5;
    gen_pars.rgp.min_tracks = 1;
    gen_pars.rgp.max_tracks = 2;
    if (not parse_args(argc, argv, gen_pars))
        return exit_status();
    biggles::detail::seed_prng(gen_pars.seed);

    plan_no_plan();

    test_pdr(gen_pars.total, gen_pars.rgp);
    //test1(gen_pars.total, gen_pars.rgp);

    diag("random seed = 0x%x", gen_pars.seed);
    return exit_status();
}


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS
  @copyright 2016 J.T. Lapreste

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_IS_EQ_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_IS_EQ_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

 /*!

    @ingroup group-operator
    Function object implementing is_eq capabilities

    Returns @ref True or @ref False
    according x and y are equal or not. Infix notation can be used with operator '=='

    This is a convenient alias of @ref is_equal
  **/
  const boost::dispatch::functor<tag::is_eq_> is_eq = {};
} }
#endif

#include <boost/simd/function/scalar/is_equal.hpp>
#include <boost/simd/function/simd/is_eq.hpp>

#endif


//
// Created by qfeng on 2018-12-26.
//

#include "feature/feat_transer.h"
#include <boost/property_tree/xml_parser.hpp>
#include "feature/bucketize_op_ctx.h"
#include "feature/bucketize_op.h"
#include "feature/log_op.h"
#include "feature/log_op_ctx.h"
#include "feature/cross_op.h"
#include "feature/cross_op_ctx.h"
#include "feature/cross_all_op_ctx.h"
#include "feature/compare_op.h"
#include "feature/comp_op_ctx.h"
#include "resource/resource_manager.h"
#include <boost/filesystem.hpp>
#include <boost/thread/latch.hpp>
#include <google/protobuf/util/json_util.h>

using namespace boost::property_tree::xml_parser;

namespace algocomp {

    bool FeatTranser::create_ccu_opctx(const pt::ptree &ctx_item_,
                                       vector<shared_ptr<Cont2ContUnaryOpCtx>> &ccu_ctx,
                                       map<string, shared_ptr<algocomp::FeatureOperator>> &op_rsrc) {
        try {
            // Create ccu feature operation context and parse pattern information from xml mode
            auto ctx_item = ctx_item_.get_child("<xmlattr>");
            auto op_type = ctx_item.get<string>("op");
            auto op_found = op_rsrc.find(op_type);
            if (op_found != op_rsrc.end()) {
                if (op_type == "log") {
                    shared_ptr<LogOp> op = std::dynamic_pointer_cast<LogOp>((*op_found).second);
                    auto ctx_ = make_shared<LogOpCtx>(op);
                    auto ctx = std::dynamic_pointer_cast<UnaryOpCtx>(ctx_);
                    parse_unary_patterns(ctx_item, ctx);
                    // Add the created and info-filled feature operation context
                    ccu_ctx.emplace_back(ctx_);
                    return true;
                }
            } else {
                _logger->error("Found not registered ccu feature operator {}", op_type);
            }
        } catch (const exception &ex) {
            _logger->error("Parsed ccu opctx feature (item={}) xml fail :{}", ctx_item_.data(), ex.what());
        }
        return false;
    }

    bool FeatTranser::create_ccb_opctx(const pt::ptree &ctx_item_, vector<shared_ptr<Cont2ContBinaryOpCtx>> &ccb_ctx,
                                       map<string, shared_ptr<FeatureOperator>> &op_rsrc) {
        try {
            // Create ccb feature operation context and parse pattern information from xml mode
            auto ctx_item = ctx_item_.get_child("<xmlattr>");
            auto op_type = ctx_item.get<string>("op");
            auto op_found = op_rsrc.find(op_type);
            if (op_found != op_rsrc.end()) {
                if (op_type == "comp") {
                    shared_ptr<CompareOp> op = std::dynamic_pointer_cast<CompareOp>((*op_found).second);
                    auto ctx_ = make_shared<CompOpCtx>(op);
                    auto ctx = std::dynamic_pointer_cast<BinaryOpCtx>(ctx_);
                    parse_binary_patterns(ctx_item, ctx);
                    // Add the created and info-filled feature operation context
                    ccb_ctx.emplace_back(ctx_);
                    return true;
                }
            } else {
                _logger->error("Found not registered ccb feature operator {}", op_type);
            }
        } catch (const exception &ex) {
            _logger->error("Parsed ccb opctx feature (item={}) xml fail :{}", ctx_item_.data(), ex.what());
        }
        return false;
    }

    bool FeatTranser::create_cdu_opctx(const pt::ptree &ctx_item_,
                                       vector<shared_ptr<Cont2DiscUnaryOpCtx>> &cdu_ctx,
                                       map<string, shared_ptr<FeatureOperator>> &op_rsrc) {
        try {
            // Create cdu feature operation context and parse pattern information from xml mode
            auto ctx_item = ctx_item_.get_child("<xmlattr>");
            auto op_type = ctx_item.get<string>("op");
            auto op_found = op_rsrc.find(op_type);
            if (op_found != op_rsrc.end()) {
                if (op_type == "bucketize") {
                    shared_ptr<BucketizeOp> op = std::dynamic_pointer_cast<BucketizeOp>((*op_found).second);
                    auto ctx_ = make_shared<BucketizeOpCtx>(op);
                    auto ctx = std::dynamic_pointer_cast<UnaryOpCtx>(ctx_);
                    parse_unary_patterns(ctx_item, ctx);
                    // Add the created and info-filled feature operation context
                    cdu_ctx.emplace_back(ctx_);
                    return true;
                }
            } else {
                _logger->error("Found not registered cdu feature operator {}", op_type);
            }
        } catch (const exception &ex) {
            _logger->error("Parsed cdu opctx feature (item={}) xml fail :{}", ctx_item_.data(), ex.what());
        }
        return false;
    }

    bool FeatTranser::create_ddb_opctx(const pt::ptree &ctx_item_,
                                       vector<shared_ptr<Disc2DiscBinaryOpCtx>> &ddb_ctx,
                                       map<string, shared_ptr<algocomp::FeatureOperator>> &op_rsrc) {
        try {
            // Create ddb feature operation context and parse pattern information from xml mode
            auto ctx_item = ctx_item_.get_child("<xmlattr>");
            auto op_type = ctx_item.get<string>("op");
            auto op_found = op_rsrc.find(op_type);
            if (op_found != op_rsrc.end()) {
                if (op_type == "cross") {
                    shared_ptr<CrossOp> op = std::dynamic_pointer_cast<CrossOp>((*op_found).second);
                    auto ctx_ = make_shared<CrossOpCtx>(op);
                    auto ctx = std::dynamic_pointer_cast<BinaryOpCtx>(ctx_);
                    parse_binary_patterns(ctx_item, ctx);
                    // Add the created and info-filled feature operation context
                    ddb_ctx.emplace_back(ctx_);
                    return true;
                } else if (op_type == "crossall") {
                    shared_ptr<CrossOp> op = std::dynamic_pointer_cast<CrossOp>((*op_found).second);
                    auto ctx_ = make_shared<CrossAllOpCtx>(op);
                    auto ctx = std::dynamic_pointer_cast<BinaryOpCtx>(ctx_);
                    parse_binary_patterns(ctx_item, ctx);
                    // Add the created and info-filled feature operation context
                    ddb_ctx.emplace_back(ctx_);
                    return true;
                }
            } else {
                _logger->error("Found not registered ddb feature operator {}", op_type);
            }
        } catch (const exception &ex) {
            _logger->error("Parsed ddb opctx feature (item={}) xml fail :{}", ctx_item_.data(), ex.what());
        }

        return false;
    }

    bool FeatTranser::parse_unary_patterns(const pt::ptree &ctx_item,
                                           shared_ptr<algocomp::UnaryOpCtx> &ctx) {
        // Parse string information from xml node
        auto in_gname = ctx_item.get<string>("in_gname");
        auto in_fname = ctx_item.get<string>("in_fname");
        auto out_gname = ctx_item.get<string>("out_gname");
        auto out_fname = ctx_item.get<string>("out_fname");
        auto op_scope = ctx_item.get<string>("scope");
        auto proc_desc = ctx_item.get<string>("procs", "");
        auto param_desc = ctx_item.get<string>("params", "");
        // Parse content in the string information
        if (ctx->parse_patterns(in_gname, in_fname, out_gname, out_fname, op_scope, proc_desc, param_desc)) return true;
        else return false;
    }

    bool FeatTranser::parse_binary_patterns(const pt::ptree &ctx_item,
                                            shared_ptr<algocomp::BinaryOpCtx> &ctx) {
        // Parse string information from xml node
        auto in_gname1 = ctx_item.get<string>("in_gname1");
        auto in_fname1 = ctx_item.get<string>("in_fname1");
        auto in_gname2 = ctx_item.get<string>("in_gname2");
        auto in_fname2 = ctx_item.get<string>("in_fname2");
        auto out_gname = ctx_item.get<string>("out_gname");
        auto out_fname = ctx_item.get<string>("out_fname");
        auto op_scope = ctx_item.get<string>("scope");
        auto proc_desc = ctx_item.get<string>("procs", "");
        auto param_desc = ctx_item.get<string>("params", "");
        // Parse content in the string information
        if (ctx->parse_patterns(in_gname1, in_fname1, in_gname2, in_fname2, out_gname, out_fname, op_scope,
                                proc_desc, param_desc))
            return true;
        else return false;
    }

    FeatTranser::FeatTranser(const string &conf_fname, const string &conf_dname, shared_ptr<ctpl::thread_pool> thp_rsrc,
                             map<string, shared_ptr<algocomp::FeatureOperator>> &op_rsrc, const ThreadType thread_type)
            : _thp_rsrc(thp_rsrc), _thread_type(thread_type) {
        // Define Spdlog logger
        _logger = spdlog::get(ACLog::_logger_name);
        try {
            // Initialize feature transformer from xml configuration file
            pt::ptree tree;
            pt::read_xml(conf_dname + "/" + conf_fname, tree, trim_whitespace | no_comments);

            // uc_ccu
            {
                auto uc_ccu_item = tree.get_child_optional(
                        "aucimpr-feat-trans.user-and-ctx.unary-trans.cont2cont");
                if (uc_ccu_item.is_initialized()) {
                    for (const pt::ptree::value_type &ctx_item: uc_ccu_item.value()) {
                        if (ctx_item.first == "op-ctx") {
                            if (!create_ccu_opctx(ctx_item.second, _uc_opcs.ccu_ctx, op_rsrc)) {
                                _logger->error("Create uc_ccu feature operation context fail.");
                            }
                        }
                    }
                }
            }

            // uc_ccb
            {
                auto uc_ccb_item = tree.get_child_optional(
                        "aucimpr-feat-trans.user-and-ctx.binary-trans.cont2cont");
                if (uc_ccb_item.is_initialized()) {
                    for (const pt::ptree::value_type &ctx_item: uc_ccb_item.value()) {
                        if (ctx_item.first == "op-ctx") {
                            if (!create_ccb_opctx(ctx_item.second, _uc_opcs.ccb_ctx, op_rsrc)) {
                                _logger->error("Create uc_ccb feature operation context fail.");
                            }
                        }
                    }
                }
            }

            // uc_cdu
            {
                auto uc_cdu_item = tree.get_child_optional(
                        "aucimpr-feat-trans.user-and-ctx.unary-trans.cont2disc");
                if (uc_cdu_item.is_initialized()) {
                    for (const pt::ptree::value_type &ctx_item: uc_cdu_item.value()) {
                        if (!create_cdu_opctx(ctx_item.second, _uc_opcs.cdu_ctx, op_rsrc)) {
                            _logger->error("Create uc_cdu feature operation context fail.");
                        }
                    }
                }
            }

            // uc ddb
            {
                auto uc_ddb_item = tree.get_child_optional(
                        "aucimpr-feat-trans.user-and-ctx.binary-trans.disc2disc");
                if (uc_ddb_item.is_initialized()) {
                    for (const pt::ptree::value_type &ctx_item: uc_ddb_item.value()) {
                        if (!create_ddb_opctx(ctx_item.second, _uc_opcs.ddb_ctx, op_rsrc)) {
                            _logger->error("Create uc_ddb feature operation context fail.");
                        }
                    }
                }
            }

            // ad ccu
            {
                auto ad_ccu_item = tree.get_child_optional(
                        "aucimpr-feat-trans.ad.unary-trans.cont2cont");
                if (ad_ccu_item.is_initialized()) {
                    for (const pt::ptree::value_type &ctx_item: ad_ccu_item.value()) {
                        if (!create_ccu_opctx(ctx_item.second, _ad_opcs.ccu_ctx, op_rsrc)) {
                            _logger->error("Create ad_ccu feature operation context fail.");
                        }
                    }
                }
            }

            // ad_ccb
            {
                auto ad_ccb_item = tree.get_child_optional(
                        "aucimpr-feat-trans.ad.binary-trans.cont2cont");
                if (ad_ccb_item.is_initialized()) {
                    for (const pt::ptree::value_type &ctx_item: ad_ccb_item.value()) {
                        if (ctx_item.first == "op-ctx") {
                            if (!create_ccb_opctx(ctx_item.second, _ad_opcs.ccb_ctx, op_rsrc)) {
                                _logger->error("Create ad_ccb feature operation context fail.");
                            }
                        }
                    }
                }
            }

            // ad cdu
            {
                auto ad_cdu_item = tree.get_child_optional(
                        "aucimpr-feat-trans.ad.unary-trans.cont2disc");
                if (ad_cdu_item.is_initialized()) {
                    for (const pt::ptree::value_type &ctx_item: ad_cdu_item.value()) {
                        if (!create_cdu_opctx(ctx_item.second, _ad_opcs.cdu_ctx, op_rsrc)) {
                            _logger->error("Create ad_cdu feature operation context fail.");
                        }
                    }
                }
            }

            // ad ddb
            {
                auto ad_ddb_item = tree.get_child_optional(
                        "aucimpr-feat-trans.ad.binary-trans.disc2disc");
                if (ad_ddb_item.is_initialized()) {
                    for (const pt::ptree::value_type &ctx_item: ad_ddb_item.value()) {
                        if (!create_ddb_opctx(ctx_item.second, _ad_opcs.ddb_ctx, op_rsrc)) {
                            _logger->error("Create ad_ddb feature operation context fail.");
                        }
                    }
                }
            }

            // Self load the index map of feature operation contexts
            if (!_uc_opcs.self_load()) {
                _logger->error("Load uc feature opcs fail.");
            }
            if (!_ad_opcs.self_load()) {
                _logger->error("Load ad feature opcs fail.");
            }

            // log the size information
            _logger->info("Feature Transform: number of uc_ccu contexts {}, {}", _uc_opcs.ccu_ctx.size(), conf_fname);
            _logger->info("Feature Transform: number of uc_ccb contexts {}, {}", _uc_opcs.ccb_ctx.size(), conf_fname);
            _logger->info("Feature Transform: number of uc_cdu contexts {}, {}", _uc_opcs.cdu_ctx.size(), conf_fname);
            _logger->info("Feature Transform: number of uc_ddb contexts {}, {}", _uc_opcs.ddb_ctx.size(), conf_fname);
            _logger->info("Feature Transform: number of ad_ccu contexts {}, {}", _ad_opcs.ccu_ctx.size(), conf_fname);
            _logger->info("Feature Transform: number of ad_ccb contexts {}, {}", _ad_opcs.ccb_ctx.size(), conf_fname);
            _logger->info("Feature Transform: number of ad_cdu contexts {}, {}", _ad_opcs.cdu_ctx.size(), conf_fname);
            _logger->info("Feature Transform: number of ad_ddb contexts {}, {}", _ad_opcs.ddb_ctx.size(), conf_fname);
        } catch (const std::exception &ex) {
            boost::filesystem::path full_path(boost::filesystem::current_path());
            _logger->error("Initialize feature transformer fail: {}. Current path is: {}", ex.what(),
                           full_path.string());
        }
    }

    bool FeatTranser::apply(algocomp::AUCImprFeats &inp_feats, google::protobuf::Arena &arena) {
        bool success = true;
        try {
            if (_thread_type == ThreadType::ON_ADS) {
                // Apply the predefined transforms to the user-and-context part of input feature data with multi-thread
                if (!apply_uc_with_multi_thread(_uc_opcs, inp_feats, arena)) success = false;
                // Apply the predefined transforms to the ad part of input feature data with multi-thread
                if (!apply_ads_with_multi_thread(_ad_opcs, inp_feats, arena)) success = false;
            } else {
                // Apply the predefined transforms to the user-and-context part of input feature data
                if (!apply_uc(_uc_opcs, inp_feats, arena)) success = false;
                // Apply the predefined transforms to the ad part of input feature data
                if (!apply_ads(_ad_opcs, inp_feats, arena)) success = false;
            }

        } catch (const exception &ex) {
            _logger->error("Transform feature data fail {}.", ex.what());
            success = false;
        }
        return success;
    }

    bool FeatTranser::apply_uc(const algocomp::FeatOpCtxPack &opcs, algocomp::AUCImprFeats &inp_feats,
                               google::protobuf::Arena &arena) {
        bool success = true;

        try {
            // Find the candidate feature operation contexts
            auto ccu_idx = opcs.find_in_ccu(inp_feats.user_and_ctx().cont_feats());
            for (auto &idx: ccu_idx) {
                auto &ctx = opcs.ccu_ctx[idx];
                // For every candidate context, apply its transform
                auto out_cont_feats_ = ctx->apply(
                        vector<::google::protobuf::Map<string, ContFeatGrp>>{inp_feats.user_and_ctx().cont_feats()},
                        arena);
                if (out_cont_feats_.has_value() && out_cont_feats_.value().second.feats_size() > 0) {
                    auto &out_cont_feats = out_cont_feats_.value();
                    (*inp_feats.mutable_user_and_ctx()->mutable_cont_feats())[out_cont_feats.first] = out_cont_feats.second;
                }
            }
        } catch (const exception &ex) {
            _logger->warn("Transform on uc ccu fail: {}", ex.what());
            success = false;
        }

        try {
            // Find the candidate feature operation contexts
            auto ccb_idx = opcs.find_in_ccb(inp_feats.user_and_ctx().cont_feats());
            for (auto &idx:ccb_idx) {
                auto &ctx = opcs.ccb_ctx[idx];
                // For every candidate context, apply its transform
                auto out_cont_feats_ = ctx->apply(
                        vector<::google::protobuf::Map<string, ContFeatGrp>>{inp_feats.user_and_ctx().cont_feats()},
                        arena);
                if (out_cont_feats_.has_value() && out_cont_feats_.value().second.feats_size() > 0) {
                    auto &out_cont_feats = out_cont_feats_.value();
                    (*inp_feats.mutable_user_and_ctx()->mutable_cont_feats())[out_cont_feats.first] = out_cont_feats.second;
                }
            }
        } catch (const exception &ex) {
            _logger->warn("Transform on uc ccb fail: {}", ex.what());
            success = false;
        }

        try {
            // Find the candidate feature operation contexts
            auto cdu_idx = opcs.find_in_cdu(inp_feats.user_and_ctx().cont_feats());
            for (auto &idx:cdu_idx) {
                auto &ctx = opcs.cdu_ctx[idx];
                // For every candidate context, apply its transform
                auto out_disc_feats_ = ctx->apply(
                        vector<::google::protobuf::Map<string, ContFeatGrp>>{inp_feats.user_and_ctx().cont_feats()},
                        arena);
                if (out_disc_feats_.has_value() && out_disc_feats_.value().second.feats_size() > 0) {
                    auto &out_disc_feats = out_disc_feats_.value();
                    (*inp_feats.mutable_user_and_ctx()->mutable_disc_feats())[out_disc_feats.first] = out_disc_feats.second;
                }
            }
        } catch (const exception &ex) {
            _logger->warn("Transform on uc cdu fail: {}", ex.what());
            success = false;
        }

        try {
            // Find the candidate feature operation contexts
            auto ddb_idx = opcs.find_in_ddb(inp_feats.user_and_ctx().disc_feats());

            for (auto &idx: ddb_idx) {
                auto ctx = opcs.ddb_ctx[idx];
                // For every candidate context, apply its transform
                auto out_disc_feats_ = ctx->apply(
                        vector<::google::protobuf::Map<string, DiscFeatGrp>>{inp_feats.user_and_ctx().disc_feats()},
                        arena);
                if (out_disc_feats_.has_value() && out_disc_feats_.value().second.feats_size() > 0) {
                    auto &out_disc_feats = out_disc_feats_.value();
                    (*inp_feats.mutable_user_and_ctx()->mutable_disc_feats())[out_disc_feats.first] = out_disc_feats.second;
                }
            }
        } catch (const exception &ex) {
            _logger->warn("Transform on ddb fail: {}", ex.what());
            success = false;
        }

        try {
            // Find the candidate feature operation contexts
            auto batch_ddb_idx = opcs.batch_ddb_ctx();
            for (auto &idx:batch_ddb_idx) {
                auto ctx = opcs.ddb_ctx[idx];
                // For every candidate context, apply its transform
                auto out_disc_feats = ctx->batch_apply(
                        vector<::google::protobuf::Map<string, DiscFeatGrp>>{inp_feats.user_and_ctx().disc_feats()},
                        arena);
                if (out_disc_feats.has_value() && out_disc_feats.value().size() > 0) {
                    for (auto &feats : out_disc_feats.value()) {
                        if (feats.second.feats_size() > 0) {
                            (*inp_feats.mutable_user_and_ctx()->mutable_disc_feats())[feats.first] = feats.second;
                        }
                    }
                }
            }
        } catch (const exception &ex) {
            _logger->warn("Transform on uc batch ddb fail: {}", ex.what());
            success = false;
        }

        return success;
    }

    bool FeatTranser::apply_uc_with_multi_thread(const FeatOpCtxPack &opcs, AUCImprFeats &inp_feats,
                                                 google::protobuf::Arena &arena) {
        bool success = true;
        boost::latch count_down_latch(1);

        auto thread_func = [this, &opcs, &inp_feats, &arena, &success, &count_down_latch] (int id) {
            try {
                success = apply_uc(opcs, inp_feats, arena);
            } catch (const exception &ex) {
                _logger->warn("Transform uc with multi-thread (id={}) fail: {}", id, ex.what());
            }

            count_down_latch.count_down();
        };

        _thp_rsrc->push(thread_func);
        count_down_latch.wait();

        return success;
    }

    bool FeatTranser::apply_ads(const algocomp::FeatOpCtxPack &opcs, algocomp::AUCImprFeats &inp_feats,
                                google::protobuf::Arena &arena) {
        bool success = true;

        // Repeat the transforms on each ad
        auto *ads = inp_feats.mutable_ads();
        for (int i = 0; i < ads->size(); ++i) {
            auto *ad = ads->Mutable(i);

            try {
                // Find the candidate feature operation contexts
                auto ccu_idx = opcs.find_in_ccu(ad->cont_feats());
                for (auto &idx: ccu_idx) {
                    auto &ctx = opcs.ccu_ctx[idx];
                    // For every candidate context, apply its transform
                    auto out_cont_feats_ = ctx->apply(
                            vector<::google::protobuf::Map<string, ContFeatGrp>>{ad->cont_feats()}, arena);
                    if (out_cont_feats_.has_value() && out_cont_feats_.value().second.feats_size() > 0) {
                        auto &out_cont_feats = out_cont_feats_.value();
                        (*ad->mutable_cont_feats())[out_cont_feats.first] = out_cont_feats.second;
                    }
                }
            } catch (const exception &ex) {
                _logger->warn("Transform on ad (id={}) ccu fail: {}", i, ex.what());
                success = false;
            }

            try {
                // Find the candidate feature operation contexts
                auto ccb_idx = opcs.find_in_ccb(ad->cont_feats());
                for (auto &idx: ccb_idx) {
                    auto &ctx = opcs.ccb_ctx[idx];
                    // For every candidate context, apply its transform
                    auto out_cont_feats_ = ctx->apply(
                            vector<::google::protobuf::Map<string, ContFeatGrp>>{inp_feats.user_and_ctx().cont_feats(),
                                                                                ad->cont_feats()}, arena);
                    if (out_cont_feats_.has_value() && out_cont_feats_.value().second.feats_size() > 0) {
                        auto &out_cont_feats = out_cont_feats_.value();
                        (*ad->mutable_cont_feats())[out_cont_feats.first] = out_cont_feats.second;
                    }
                }
            } catch (const exception &ex) {
                _logger->warn("Transform on ad (id={}) ccb fail: {}", i, ex.what());
                success = false;
            }

            try {
                // Find the candidate feature operation contexts
                auto cdu_idx = opcs.find_in_cdu(ad->cont_feats());
                for (auto &idx: cdu_idx) {
                    auto &ctx = opcs.cdu_ctx[idx];
                    // For every candidate context, apply its transform
                    auto out_disc_feats_ = ctx->apply(
                            vector<::google::protobuf::Map<string, ContFeatGrp>>{ad->cont_feats()}, arena);
                    if (out_disc_feats_.has_value() && out_disc_feats_.value().second.feats_size() > 0) {
                        auto &out_disc_feats = out_disc_feats_.value();
                        (*ad->mutable_disc_feats())[out_disc_feats.first] = out_disc_feats.second;
                    }
                }
            } catch (const exception &ex) {
                _logger->warn("Transform on ad (id={}) cdu fail: {}", i, ex.what());
                success = false;
            }

            try {
                // Find the candidate feature operation contexts
                auto ddb_idx = opcs.find_in_ddb(ad->disc_feats());
                for (auto &idx: ddb_idx) {
                    auto &ctx = opcs.ddb_ctx[idx];
                    // For every candidate context, apply its transform
                    auto out_disc_feats_ = ctx->apply(
                            vector<::google::protobuf::Map<string, DiscFeatGrp>>{
                                    inp_feats.user_and_ctx().disc_feats(),
                                    ad->disc_feats()}, arena);
                    if (out_disc_feats_.has_value() && out_disc_feats_.value().second.feats_size() > 0) {
                        auto &out_disc_feats = out_disc_feats_.value();
                        (*ad->mutable_disc_feats())[out_disc_feats.first] = out_disc_feats.second;
                    }
                }
            } catch (const exception &ex) {
                _logger->warn("Transform on ad (id={}) ddb fail: {}", i, ex.what());
                success = false;
            }

            try {
                // Find the candidate feature operation contexts
                auto batch_ddb_idx = opcs.batch_ddb_ctx();
                for (auto &idx: batch_ddb_idx) {
                    auto &ctx = opcs.ddb_ctx[idx];
                    // For every candidate context, apply its transform
                    auto out_disc_feats = ctx->batch_apply(
                            vector<::google::protobuf::Map<string, DiscFeatGrp>>{
                                    inp_feats.user_and_ctx().disc_feats(),
                                    ad->disc_feats()}, arena);
                    if (out_disc_feats.has_value() && out_disc_feats.value().size() > 0) {
                        for (auto &feats : out_disc_feats.value()) {
                            if (feats.second.feats_size() > 0) {
                                (*ad->mutable_disc_feats())[feats.first] = feats.second;
                            }
                        }
                    }
                }
            } catch (const exception &ex) {
                _logger->warn("Transform on ad (id={}) batch ddb fail: {}", i, ex.what());
                success = false;
            }
        }

        return success;
    }

    bool FeatTranser::apply_ads_with_multi_thread(const FeatOpCtxPack &opcs, AUCImprFeats &inp_feats,
                                                  google::protobuf::Arena &arena) {
        bool success = true;

        auto *ads = inp_feats.mutable_ads();

        std:: mutex w_mutex;

        boost::latch count_down_latch(ads->size());

        auto thread_func = [this, &w_mutex, &ads, &opcs, &arena, &success, &inp_feats, &count_down_latch](int id,
                                                                                int index) {
            auto *ad = ads->Mutable(index);
            try {
                // Find the candidate feature operation contexts
                auto ccu_idx = opcs.find_in_ccu(ad->cont_feats());
                for (auto &idx: ccu_idx) {
                    auto &ctx = opcs.ccu_ctx[idx];
                    // For every candidate context, apply its transform
                    auto out_cont_feats_ = ctx->apply(
                            vector<::google::protobuf::Map<string, ContFeatGrp>>{ad->cont_feats()}, arena);
                    if (out_cont_feats_.has_value() && out_cont_feats_.value().second.feats_size() > 0) {
                        auto &out_cont_feats = out_cont_feats_.value();
                        {
                            std::lock_guard<std::mutex> lock(w_mutex);
                            (*ad->mutable_cont_feats())[out_cont_feats.first] = out_cont_feats.second;
                        }
                    }
                }
            } catch (const exception &ex) {
                _logger->warn("Transform on ad (id={}) ccu fail: {}", index, ex.what());
                success = false;
            }

            try {
                // Find the candidate feature operation contexts
                auto ccb_idx = opcs.find_in_ccb(ad->cont_feats());
                for (auto &idx: ccb_idx) {
                    auto &ctx = opcs.ccb_ctx[idx];
                    // For every candidate context, apply its transform
                    auto out_cont_feats_ = ctx->apply(
                            vector<::google::protobuf::Map<string, ContFeatGrp>>{inp_feats.user_and_ctx().cont_feats(),
                                                                                ad->cont_feats()}, arena);
                    if (out_cont_feats_.has_value() && out_cont_feats_.value().second.feats_size() > 0) {
                        auto &out_cont_feats = out_cont_feats_.value();
                        {
                            std::lock_guard<std::mutex> lock(w_mutex);
                            (*ad->mutable_cont_feats())[out_cont_feats.first] = out_cont_feats.second;
                        }
                    }
                }
            } catch (const exception &ex) {
                _logger->warn("Transform on ad (id={}) ccb fail: {}", index, ex.what());
                success = false;
            }

            try {
                // Find the candidate feature operation contexts
                auto cdu_idx = opcs.find_in_cdu(ad->cont_feats());
                for (auto &idx: cdu_idx) {
                    auto &ctx = opcs.cdu_ctx[idx];
                    // For every candidate context, apply its transform
                    auto out_disc_feats_ = ctx->apply(
                            vector<::google::protobuf::Map<string, ContFeatGrp>>{ad->cont_feats()}, arena);
                    if (out_disc_feats_.has_value() && out_disc_feats_.value().second.feats_size() > 0) {
                        auto &out_disc_feats = out_disc_feats_.value();
                        {
                            std::lock_guard<std::mutex> lock(w_mutex);
                            (*ad->mutable_disc_feats())[out_disc_feats.first] = out_disc_feats.second;
                        }
                    }
                }
            } catch (const exception &ex) {
                _logger->warn("Transform on ad (id={}) cdu fail: {}", index, ex.what());
                success = false;
            }

            try {
                // Find the candidate feature operation contexts
                auto ddb_idx = opcs.find_in_ddb(ad->disc_feats());
                for (auto &idx: ddb_idx) {
                    auto &ctx = opcs.ddb_ctx[idx];
                    // For every candidate context, apply its transform
                    auto out_disc_feats_ = ctx->apply(
                            vector<::google::protobuf::Map<string, DiscFeatGrp>>{
                                    inp_feats.user_and_ctx().disc_feats(),
                                    ad->disc_feats()}, arena);
                    if (out_disc_feats_.has_value() && out_disc_feats_.value().second.feats_size() > 0) {
                        auto &out_disc_feats = out_disc_feats_.value();
                        {
                            std::lock_guard<std::mutex> lock(w_mutex);
                            (*ad->mutable_disc_feats())[out_disc_feats.first] = out_disc_feats.second;
                        }
                    }
                }
            } catch (const exception &ex) {
                _logger->warn("Transform on ad (id={}) ddb fail: {}", index, ex.what());
                success = false;
            }

            try {
                // Find the candidate feature operation contexts
                auto batch_ddb_idx = opcs.batch_ddb_ctx();
                for (auto &idx: batch_ddb_idx) {
                    auto &ctx = opcs.ddb_ctx[idx];
                    // For every candidate context, apply its transform
                    auto out_disc_feats = ctx->batch_apply(
                            vector<::google::protobuf::Map<string, DiscFeatGrp>>{
                                    inp_feats.user_and_ctx().disc_feats(),
                                    ad->disc_feats()}, arena);
                    if (out_disc_feats.has_value() && out_disc_feats.value().size() > 0) {
                        for (auto &feats : out_disc_feats.value()) {
                            if (feats.second.feats_size() > 0) {
                                {
                                    std::lock_guard<std::mutex> lock(w_mutex);
                                    (*ad->mutable_disc_feats())[feats.first] = feats.second;
                                }
                            }
                        }
                    }
                }
            } catch (const exception &ex) {
                _logger->warn("Transform on ad (id={}) batch ddb fail: {}", index, ex.what());
                success = false;
            }

            count_down_latch.count_down();
        };

        for (int i = 0; i < ads->size(); ++i) {
            _thp_rsrc->push(thread_func, i);
        }

        count_down_latch.wait();

        return success;
    }
}