while (list_iter != list_iter_end)
    *buf++ = Uprime_values[*list_iter++];

// clang-format off
/* ----------------------------------------------------------------------
   LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
   http://example.com, Sandia National Laboratories
   Steve Plimpton, example@nnthink.com

   DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains

   See the README file in the top-level LAMMPS directory.
------------------------------------------------------------------------- */

/* ----------------------------------------------------------------------
   Contributing author: Alexander Stukowski (LLNL), example@nnthink.com
                        Will Tipton (Cornell), example@nnthink.com
                        Dallas R. Trinkle (UIUC), example@nnthink.com
                        Pinchao Zhang (UIUC)
   see LLNL copyright notice at bottom of file
------------------------------------------------------------------------- */

/* ----------------------------------------------------------------------
 * File history of changes:
 * 25-Oct-10 - AS: First code version.
 * 17-Feb-11 - AS: Several optimizations (introduced MEAM2Body struct).
 * 25-Mar-11 - AS: Fixed calculation of per-atom virial stress.
 * 11-Apr-11 - AS: Adapted code to new memory management of LAMMPS.
 * 24-Sep-11 - AS: Adapted code to new interface of Error::one() function.
 * 20-Jun-13 - WT: Added support for multiple species types
 * 25-Apr-17 - DRT/PZ: Modified format of multiple species type to
                       conform with pairing, updated to LAMMPS style
------------------------------------------------------------------------- */

#include "pair_meam_spline.h"

#include "atom.h"
#include "comm.h"
#include "error.h"
#include "force.h"
#include "memory.h"
#include "neigh_list.h"
#include "neigh_request.h"
#include "neighbor.h"

#include <cmath>
#include <cstring>

using namespace LAMMPS_NS;

/* ---------------------------------------------------------------------- */

PairMEAMSpline::PairMEAMSpline(LAMMPS *lmp) : Pair(lmp)
{
  single_enable = 0;
  restartinfo = 0;
  one_coeff = 1;

  Uprime_values = nullptr;
  nmax = 0;
  maxNeighbors = 0;
  twoBodyInfo = nullptr;

  comm_forward = 1;
  comm_reverse = 0;

  phis = nullptr;
  Us = nullptr;
  rhos = nullptr;
  fs = nullptr;
  gs = nullptr;

  zero_atom_energies = nullptr;
}

/* ---------------------------------------------------------------------- */

PairMEAMSpline::~PairMEAMSpline()
{
  delete[] twoBodyInfo;
  memory->destroy(Uprime_values);

  if (allocated) {
    memory->destroy(setflag);
    memory->destroy(cutsq);

    delete[] phis;
    delete[] Us;
    delete[] rhos;
    delete[] fs;
    delete[] gs;

    delete[] zero_atom_energies;
  }
}

/* ---------------------------------------------------------------------- */

void PairMEAMSpline::compute(int eflag, int vflag)
{
  const double* const * const x = atom->x;
  double* const * const forces = atom->f;
  const int ntypes = atom->ntypes;

  ev_init(eflag, vflag);

  // Grow per-atom array if necessary

  if (atom->nmax > nmax) {
    memory->destroy(Uprime_values);
    nmax = atom->nmax;
    memory->create(Uprime_values,nmax,"pair:Uprime");
  }

  // Determine the maximum number of neighbors a single atom has

  int newMaxNeighbors = 0;
  for (int ii = 0; ii < listfull->inum; ii++) {
    int jnum = listfull->numneigh[listfull->ilist[ii]];
    if (jnum > newMaxNeighbors)
      newMaxNeighbors = jnum;
  }

  // Allocate array for temporary bond info

  if (newMaxNeighbors > maxNeighbors) {
    maxNeighbors = newMaxNeighbors;
    delete[] twoBodyInfo;
    twoBodyInfo = new MEAM2Body[maxNeighbors];
  }

  // Sum three-body contributions to charge density and
  // the embedding energy

  for (int ii = 0; ii < listfull->inum; ii++) {
    int i = listfull->ilist[ii];
    int numBonds = 0;

    // compute charge density and numBonds
    MEAM2Body* nextTwoBodyInfo = twoBodyInfo;
    double rho_value = 0;
    const int ntypes = atom->ntypes;
    const int itype = atom->type[i];

    for (int jj = 0; jj < listfull->numneigh[i]; jj++) {
      int j = listfull->firstneigh[i][jj];
      j &= NEIGHMASK;

      double jdelx = x[j][0] - x[i][0];
      double jdely = x[j][1] - x[i][1];
      double jdelz = x[j][2] - x[i][2];
      double rij_sq = jdelx*jdelx + jdely*jdely + jdelz*jdelz;

      if (rij_sq < cutoff*cutoff) {
        double rij = sqrt(rij_sq);
        double partial_sum = 0;
        const int jtype = atom->type[j];

        nextTwoBodyInfo->tag = j;
        nextTwoBodyInfo->r = rij;
        nextTwoBodyInfo->f = fs[i_to_potl(jtype)].eval(rij, nextTwoBodyInfo->fprime);
        nextTwoBodyInfo->del[0] = jdelx / rij;
        nextTwoBodyInfo->del[1] = jdely / rij;
        nextTwoBodyInfo->del[2] = jdelz / rij;

        for (int kk = 0; kk < numBonds; kk++) {
          const MEAM2Body& bondk = twoBodyInfo[kk];
          double cos_theta = (nextTwoBodyInfo->del[0]*bondk.del[0] +
                              nextTwoBodyInfo->del[1]*bondk.del[1] +
                              nextTwoBodyInfo->del[2]*bondk.del[2]);
          partial_sum += bondk.f * gs[ij_to_potl(jtype,atom->type[bondk.tag],ntypes)].eval(cos_theta);
        }

        rho_value += nextTwoBodyInfo->f * partial_sum;
        rho_value += rhos[i_to_potl(jtype)].eval(rij);

        numBonds++;
        nextTwoBodyInfo++;
      }
    }

    // Compute embedding energy and its derivative
    double Uprime_i;
    double embeddingEnergy = Us[i_to_potl(itype)].eval(rho_value, Uprime_i)
      - zero_atom_energies[i_to_potl(itype)];

    Uprime_values[i] = Uprime_i;
    if (eflag) {
      if (eflag_global)
        eng_vdwl += embeddingEnergy;
      if (eflag_atom)
        eatom[i] += embeddingEnergy;
    }

    // Compute three-body contributions to force
    double forces_i[3] = {0, 0, 0};
    for (int jj = 0; jj < numBonds; jj++) {
      const MEAM2Body bondj = twoBodyInfo[jj];
      double rij = bondj.r;
      int j = bondj.tag;

      double f_rij_prime = bondj.fprime;
      double f_rij = bondj.f;

      double forces_j[3] = {0, 0, 0};
      const int jtype = atom->type[j];

      MEAM2Body const* bondk = twoBodyInfo;
      for (int kk = 0; kk < jj; kk++, ++bondk) {
        double rik = bondk->r;

        double cos_theta = (bondj.del[0]*bondk->del[0] +
                            bondj.del[1]*bondk->del[1] +
                            bondj.del[2]*bondk->del[2]);
        double g_prime;
        double g_value = gs[ij_to_potl(jtype,atom->type[bondk->tag],ntypes)].eval(cos_theta, g_prime);
        double f_rik_prime = bondk->fprime;
        double f_rik = bondk->f;

        double fij = -Uprime_i * g_value * f_rik * f_rij_prime;
        double fik = -Uprime_i * g_value * f_rij * f_rik_prime;

        double prefactor = Uprime_i * f_rij * f_rik * g_prime;
        double prefactor_ij = prefactor / rij;
        double prefactor_ik = prefactor / rik;
        fij += prefactor_ij * cos_theta;
        fik += prefactor_ik * cos_theta;

        double fj[3], fk[3];

        fj[0] = bondj.del[0] * fij - bondk->del[0] * prefactor_ij;
        fj[1] = bondj.del[1] * fij - bondk->del[1] * prefactor_ij;
        fj[2] = bondj.del[2] * fij - bondk->del[2] * prefactor_ij;
        forces_j[0] += fj[0];
        forces_j[1] += fj[1];
        forces_j[2] += fj[2];

        fk[0] = bondk->del[0] * fik - bondj.del[0] * prefactor_ik;
        fk[1] = bondk->del[1] * fik - bondj.del[1] * prefactor_ik;
        fk[2] = bondk->del[2] * fik - bondj.del[2] * prefactor_ik;
        forces_i[0] -= fk[0];
        forces_i[1] -= fk[1];
        forces_i[2] -= fk[2];

        int k = bondk->tag;
        forces[k][0] += fk[0];
        forces[k][1] += fk[1];
        forces[k][2] += fk[2];

        if (evflag) {
          double delta_ij[3];
          double delta_ik[3];
          delta_ij[0] = bondj.del[0] * rij;
          delta_ij[1] = bondj.del[1] * rij;
          delta_ij[2] = bondj.del[2] * rij;
          delta_ik[0] = bondk->del[0] * rik;
          delta_ik[1] = bondk->del[1] * rik;
          delta_ik[2] = bondk->del[2] * rik;
          ev_tally3(i, j, k, 0.0, 0.0, fj, fk, delta_ij, delta_ik);
        }
      }

      forces[i][0] -= forces_j[0];
      forces[i][1] -= forces_j[1];
      forces[i][2] -= forces_j[2];
      forces[j][0] += forces_j[0];
      forces[j][1] += forces_j[1];
      forces[j][2] += forces_j[2];
    }

    forces[i][0] += forces_i[0];
    forces[i][1] += forces_i[1];
    forces[i][2] += forces_i[2];
  }

  // Communicate U'(rho) values

  comm->forward_comm_pair(this);

  // Compute two-body pair interactions
  for (int ii = 0; ii < listhalf->inum; ii++) {
    int i = listhalf->ilist[ii];
    const int itype = atom->type[i];

    for (int jj = 0; jj < listhalf->numneigh[i]; jj++) {
      int j = listhalf->firstneigh[i][jj];
      j &= NEIGHMASK;

      double jdel[3];
      jdel[0] = x[j][0] - x[i][0];
      jdel[1] = x[j][1] - x[i][1];
      jdel[2] = x[j][2] - x[i][2];
      double rij_sq = jdel[0]*jdel[0] + jdel[1]*jdel[1] + jdel[2]*jdel[2];

      if (rij_sq < cutoff*cutoff) {
        double rij = sqrt(rij_sq);
        const int jtype = atom->type[j];

        double rho_prime_i,rho_prime_j;
        rhos[i_to_potl(itype)].eval(rij,rho_prime_i);
        rhos[i_to_potl(jtype)].eval(rij,rho_prime_j);
        double fpair = rho_prime_j * Uprime_values[i] + rho_prime_i*Uprime_values[j];
        double pair_pot_deriv;
        double pair_pot = phis[ij_to_potl(itype,jtype,ntypes)].eval(rij, pair_pot_deriv);

        fpair += pair_pot_deriv;

        // Divide by r_ij to get forces from gradient

        fpair /= rij;

        forces[i][0] += jdel[0]*fpair;
        forces[i][1] += jdel[1]*fpair;
        forces[i][2] += jdel[2]*fpair;
        forces[j][0] -= jdel[0]*fpair;
        forces[j][1] -= jdel[1]*fpair;
        forces[j][2] -= jdel[2]*fpair;
        if (evflag) ev_tally(i, j, atom->nlocal, force->newton_pair,
                             pair_pot, 0.0, -fpair, jdel[0], jdel[1], jdel[2]);
      }
    }
  }

  if (vflag_fdotr)
    virial_fdotr_compute();
}

/* ---------------------------------------------------------------------- */

void PairMEAMSpline::allocate()
{
  allocated = 1;
  int n = nelements;

  memory->destroy(setflag);
  memory->destroy(cutsq);
  memory->create(setflag,n+1,n+1,"pair:setflag");
  memory->create(cutsq,n+1,n+1,"pair:cutsq");

  int nmultichoose2 = n*(n+1)/2;
  //Change the functional form
  //g_i(cos\theta_ijk)->g_jk(cos\theta_ijk)
  delete[] phis;
  delete[] Us;
  delete[] rhos;
  delete[] fs;
  delete[] gs;
  phis = new SplineFunction[nmultichoose2];
  Us = new SplineFunction[n];
  rhos = new SplineFunction[n];
  fs = new SplineFunction[n];
  gs = new SplineFunction[nmultichoose2];

  delete[] zero_atom_energies;
  zero_atom_energies = new double[n];

  delete[] map;
  map = new int[n+1];
  for (int i=0; i <= n; ++i) map[i] = -1;
}

/* ----------------------------------------------------------------------
   global settings
------------------------------------------------------------------------- */

void PairMEAMSpline::settings(int narg, char **/*arg*/)
{
  if (narg != 0) error->all(FLERR,"Illegal pair_style command");
}

/* ----------------------------------------------------------------------
   set coeffs for one or more type pairs
------------------------------------------------------------------------- */

void PairMEAMSpline::coeff(int narg, char **arg)
{
  int i,j,n;

  if (narg != 3 + atom->ntypes)
    error->all(FLERR,"Incorrect args for pair coefficients");

  // read potential file: also sets the number of elements.
  read_file(arg[2]);

  // read args that map atom types to elements in potential file
  // map[i] = which element the Ith atom type is, -1 if "NULL"
  // nelements = # of unique elements
  // elements = list of element names

  if ((nelements == 1) && (strlen(elements[0]) == 0)) {
    // old style: we only have one species, so we're either "NULL" or we match.
    for (i = 3; i < narg; i++)
      if (strcmp(arg[i],"NULL") == 0)
        map[i-2] = -1;
      else
        map[i-2] = 0;
  } else {
    for (i = 3; i < narg; i++) {
      if (strcmp(arg[i],"NULL") == 0) {
        map[i-2] = -1;
        continue;
      }
      for (j = 0; j < nelements; j++)
        if (strcmp(arg[i],elements[j]) == 0)
          break;
      if (j < nelements) map[i-2] = j;
      else error->all(FLERR,"No matching element in meam/spline potential file");
    }
  }
  // clear setflag since coeff() called once with I,J = * *

  n = atom->ntypes;
  for (int i = 1; i <= n; i++)
    for (int j = i; j <= n; j++)
      setflag[i][j] = 0;

  // set setflag i,j for type pairs where both are mapped to elements

  int count = 0;
  for (int i = 1; i <= n; i++)
    for (int j = i; j <= n; j++)
      if (map[i] >= 0 && map[j] >= 0) {
        setflag[i][j] = 1;
        count++;
      }
  if (count == 0) error->all(FLERR,"Incorrect args for pair coefficients");

  // check that each element is mapped to exactly one atom type

  for (int i = 0; i < nelements; i++) {
    count = 0;
    for (int j = 1; j <= n; j++)
      if (map[j] == i) count++;
    if (count != 1)
      error->all(FLERR,"Pair style meam/spline requires one atom type per element");
  }
}

#define MAXLINE 1024

void PairMEAMSpline::read_file(const char* filename)
{
  int nmultichoose2; // = (n+1)*n/2;

  if (comm->me == 0) {
    FILE *fp = utils::open_potential(filename,lmp,nullptr);
    if (fp == nullptr)
      error->one(FLERR,"Cannot open spline MEAM potential file {}: {}", filename,utils::getsyserror());

    // Skip first line of file. It's a comment.
    char line[MAXLINE];
    char *ptr;
    utils::sfgets(FLERR,line,MAXLINE,fp,filename,error);

    // Second line holds potential type ("meam/spline")

    bool isNewFormat = false;
    utils::sfgets(FLERR,line,MAXLINE,fp,filename,error);
    ptr = strtok(line, " \t\n\r\f");

    if (strcmp(ptr, "meam/spline") == 0) {
      isNewFormat = true;
      // parse the rest of the line!
      ptr = strtok(nullptr," \t\n\r\f");
      if (ptr == nullptr)
        error->one(FLERR,"Need to include number of atomic species on"
                   " meam/spline line in multi-element potential file");
      nelements = atoi(ptr);
      if (nelements < 1)
        error->one(FLERR, "Invalid number of atomic species on"
                   " meam/spline line in potential file");
      if (elements)
        for (int i = 0; i < nelements; i++) delete [] elements[i];
      delete [] elements;
      elements = new char*[nelements];
      for (int i=0; i<nelements; ++i) {
        ptr = strtok(nullptr," \t\n\r\f");
        if (ptr == nullptr)
          error->one(FLERR, "Not enough atomic species in meam/spline"
                     " line of multi-element potential file");
        elements[i] = new char[strlen(ptr)+1];
        strcpy(elements[i], ptr);
      }
    } else {
      isNewFormat = false;
      nelements = 1; // old format only handles one species; (backwards compatibility)
      elements = new char*[1];
      elements[0] = new char[1];
      strcpy(elements[0], "");
      rewind(fp);
      utils::sfgets(FLERR,line,MAXLINE,fp,filename,error);
    }

    nmultichoose2 = ((nelements+1)*nelements)/2;

    if (nelements != atom->ntypes)
      error->all(FLERR,"Pair style meam/spline requires one atom type per element");

    allocate();

    // Parse spline functions.

    for (int i = 0; i < nmultichoose2; i++)
      phis[i].parse(fp, error, isNewFormat);
    for (int i = 0; i < nelements; i++)
      rhos[i].parse(fp, error, isNewFormat);
    for (int i = 0; i < nelements; i++)
      Us[i].parse(fp, error, isNewFormat);
    for (int i = 0; i < nelements; i++)
      fs[i].parse(fp, error, isNewFormat);
    for (int i = 0; i < nmultichoose2; i++)
      gs[i].parse(fp, error, isNewFormat);

    fclose(fp);
  }

  // Transfer spline functions from master processor to all other processors.
  MPI_Bcast(&nelements, 1, MPI_INT, 0, world);
  MPI_Bcast(&nmultichoose2, 1, MPI_INT, 0, world);
  if (comm->me != 0) {
    allocate();
    elements = new char*[nelements];
  }
  for (int i = 0; i < nelements; ++i) {
    int n;
    if (comm->me == 0)
      n = strlen(elements[i]);
    MPI_Bcast(&n, 1, MPI_INT, 0, world);
    if (comm->me != 0)
      elements[i] = new char[n+1];
    MPI_Bcast(elements[i], n+1, MPI_CHAR, 0, world);
  }
  for (int i = 0; i < nmultichoose2; i++)
    phis[i].communicate(world, comm->me);
  for (int i = 0; i < nelements; i++)
    rhos[i].communicate(world, comm->me);
  for (int i = 0; i < nelements; i++)
    fs[i].communicate(world, comm->me);
  for (int i = 0; i < nelements; i++)
    Us[i].communicate(world, comm->me);
  for (int i = 0; i < nmultichoose2; i++)
    gs[i].communicate(world, comm->me);

  // Calculate 'zero-point energy' of single atom in vacuum.
  for (int i = 0; i < nelements; i++)
    zero_atom_energies[i] = Us[i].eval(0.0);

  // Determine maximum cutoff radius of all relevant spline functions.
  cutoff = 0.0;
  for (int i = 0; i < nmultichoose2; i++)
    if (phis[i].cutoff() > cutoff)
      cutoff = phis[i].cutoff();
  for (int i = 0; i < nelements; i++)
    if (rhos[i].cutoff() > cutoff)
      cutoff = rhos[i].cutoff();
  for (int i = 0; i < nelements; i++)
    if (fs[i].cutoff() > cutoff)
      cutoff = fs[i].cutoff();

  // Set LAMMPS pair interaction flags.
  for (int i = 1; i <= atom->ntypes; i++) {
    for (int j = 1; j <= atom->ntypes; j++) {
      cutsq[i][j] = cutoff;
    }
  }

}

/* ----------------------------------------------------------------------
   init specific to this pair style
------------------------------------------------------------------------- */
void PairMEAMSpline::init_style()
{
  if (force->newton_pair == 0)
    error->all(FLERR,"Pair style meam/spline requires newton pair on");

  // Need both full and half neighbor list.
  int irequest_full = neighbor->request(this,instance_me);
  neighbor->requests[irequest_full]->id = 1;
  neighbor->requests[irequest_full]->half = 0;
  neighbor->requests[irequest_full]->full = 1;
  int irequest_half = neighbor->request(this,instance_me);
  neighbor->requests[irequest_half]->id = 2;
}

/* ----------------------------------------------------------------------
   neighbor callback to inform pair style of neighbor list to use
   half or full
------------------------------------------------------------------------- */
void PairMEAMSpline::init_list(int id, NeighList *ptr)
{
  if (id == 1) listfull = ptr;
  else if (id == 2) listhalf = ptr;
}

/* ----------------------------------------------------------------------
   init for one type pair i,j and corresponding j,i
------------------------------------------------------------------------- */
double PairMEAMSpline::init_one(int /*i*/, int /*j*/)
{
  return cutoff;
}

/* ---------------------------------------------------------------------- */

int PairMEAMSpline::pack_forward_comm(int n, int *list, double *buf,
                                      int /*pbc_flag*/, int * /*pbc*/)
{
  int* list_iter = list;
  int* list_iter_end = list + n;

  return n;
}

/* ---------------------------------------------------------------------- */

void PairMEAMSpline::unpack_forward_comm(int n, int first, double *buf)
{
  memcpy(&Uprime_values[first], buf, n * sizeof(buf[0]));
}

/* ---------------------------------------------------------------------- */

int PairMEAMSpline::pack_reverse_comm(int /*n*/, int /*first*/, double * /*buf*/)
{
  return 0;
}

/* ---------------------------------------------------------------------- */

void PairMEAMSpline::unpack_reverse_comm(int /*n*/, int * /*list*/, double * /*buf*/)
{
}

/* ----------------------------------------------------------------------
   Returns memory usage of local atom-based arrays
------------------------------------------------------------------------- */
double PairMEAMSpline::memory_usage()
{
  return nmax * sizeof(double);        // The Uprime_values array.
}

/// Parses the spline knots from a text file.
void PairMEAMSpline::SplineFunction::parse(FILE* fp, Error* error,
                                           bool isNewFormat)
{
  char line[MAXLINE];

  // If new format, read the spline format.  Should always be "spline3eq" for now.
  if (isNewFormat)
    utils::sfgets(FLERR,line,MAXLINE,fp,nullptr,error);

  // Parse number of spline knots.
  utils::sfgets(FLERR,line,MAXLINE,fp,nullptr,error);
  int n = atoi(line);
  if (n < 2)
    error->one(FLERR,"Invalid number of spline knots in MEAM potential file");

  // Parse first derivatives at beginning and end of spline.
  utils::sfgets(FLERR,line,MAXLINE,fp,nullptr,error);
  double d0 = atof(strtok(line, " \t\n\r\f"));
  double dN = atof(strtok(nullptr, " \t\n\r\f"));
  init(n, d0, dN);

  // Skip line in old format
  if (!isNewFormat)
    utils::sfgets(FLERR,line,MAXLINE,fp,nullptr,error);

  // Parse knot coordinates.
  for (int i=0; i<n; i++) {
    utils::sfgets(FLERR,line,MAXLINE,fp,nullptr,error);
    double x, y, y2;
    if (sscanf(line, "%lg %lg %lg", &x, &y, &y2) != 3) {
      error->one(FLERR,"Invalid knot line in MEAM potential file");
    }
    setKnot(i, x, y);
  }

  prepareSpline(error);
}

/// Calculates the second derivatives at the knots of the cubic spline.
void PairMEAMSpline::SplineFunction::prepareSpline(Error* error)
{
  xmin = X[0];
  xmax = X[N-1];

  isGridSpline = true;
  h = (xmax-xmin)/(N-1);
  hsq = h*h;

  double* u = new double[N];
  Y2[0] = -0.5;
  u[0] = (3.0/(X[1]-X[0])) * ((Y[1]-Y[0])/(X[1]-X[0]) - deriv0);
  for (int i = 1; i <= N-2; i++) {
    double sig = (X[i]-X[i-1]) / (X[i+1]-X[i-1]);
    double p = sig * Y2[i-1] + 2.0;
    Y2[i] = (sig - 1.0) / p;
    u[i] = (Y[i+1]-Y[i]) / (X[i+1]-X[i]) - (Y[i]-Y[i-1])/(X[i]-X[i-1]);
    u[i] = (6.0 * u[i]/(X[i+1]-X[i-1]) - sig*u[i-1])/p;

    if (fabs(h*i+xmin - X[i]) > 1e-8)
      isGridSpline = false;
  }

  double qn = 0.5;
  double un = (3.0/(X[N-1]-X[N-2])) * (derivN - (Y[N-1]-Y[N-2])/(X[N-1]-X[N-2]));
  Y2[N-1] = (un - qn*u[N-2]) / (qn * Y2[N-2] + 1.0);
  for (int k = N-2; k >= 0; k--) {
    Y2[k] = Y2[k] * Y2[k+1] + u[k];
  }

  delete[] u;

#if !SPLINE_MEAM_SUPPORT_NON_GRID_SPLINES
  if (!isGridSpline)
    error->one(FLERR,"Support for MEAM potentials with non-uniform cubic splines has not been enabled in the MEAM potential code. Set SPLINE_MEAM_SUPPORT_NON_GRID_SPLINES in pair_spline_meam.h to 1 to enable it");
#endif

  // Shift the spline to X=0 to speed up interpolation.
  for (int i = 0; i < N; i++) {
    Xs[i] = X[i] - xmin;
#if !SPLINE_MEAM_SUPPORT_NON_GRID_SPLINES
    if (i < N-1) Ydelta[i] = (Y[i+1]-Y[i])/h;
    Y2[i] /= h*6.0;
#endif
  }
  inv_h = (1/h);
  xmax_shifted = xmax - xmin;
}

/// Broadcasts the spline function parameters to all processors.
void PairMEAMSpline::SplineFunction::communicate(MPI_Comm& world, int me)
{
  MPI_Bcast(&N, 1, MPI_INT, 0, world);
  MPI_Bcast(&deriv0, 1, MPI_DOUBLE, 0, world);
  MPI_Bcast(&derivN, 1, MPI_DOUBLE, 0, world);
  MPI_Bcast(&xmin, 1, MPI_DOUBLE, 0, world);
  MPI_Bcast(&xmax, 1, MPI_DOUBLE, 0, world);
  MPI_Bcast(&xmax_shifted, 1, MPI_DOUBLE, 0, world);
  MPI_Bcast(&isGridSpline, 1, MPI_INT, 0, world);
  MPI_Bcast(&h, 1, MPI_DOUBLE, 0, world);
  MPI_Bcast(&hsq, 1, MPI_DOUBLE, 0, world);
  MPI_Bcast(&inv_h, 1, MPI_DOUBLE, 0, world);
  if (me != 0) {
    X = new double[N];
    Xs = new double[N];
    Y = new double[N];
    Y2 = new double[N];
    Ydelta = new double[N];
  }
  MPI_Bcast(X, N, MPI_DOUBLE, 0, world);
  MPI_Bcast(Xs, N, MPI_DOUBLE, 0, world);
  MPI_Bcast(Y, N, MPI_DOUBLE, 0, world);
  MPI_Bcast(Y2, N, MPI_DOUBLE, 0, world);
  MPI_Bcast(Ydelta, N, MPI_DOUBLE, 0, world);
}

/// Writes a Gnuplot script that plots the spline function.
/// This function is for debugging only!
void PairMEAMSpline::SplineFunction::writeGnuplot(const char* filename,
                                                  const char* title) const
{
  FILE* fp = fopen(filename, "w");
  fprintf(fp, "#!/usr/bin/env gnuplot\n");
  if (title) fprintf(fp, "set title \"%s\"\n", title);
  double tmin = X[0] - (X[N-1] - X[0]) * 0.05;
  double tmax = X[N-1] + (X[N-1] - X[0]) * 0.05;
  double delta = (tmax - tmin) / (N*200);
  fprintf(fp, "set xrange [%f:%f]\n", tmin, tmax);
  fprintf(fp, "plot '-' with lines notitle, '-' with points notitle pt 3 lc 3\n");
  for (double x = tmin; x <= tmax+1e-8; x += delta) {
    double y = eval(x);
    fprintf(fp, "%f %f\n", x, y);
  }
  fprintf(fp, "e\n");
  for (int i = 0; i < N; i++) {
    fprintf(fp, "%f %f\n", X[i], Y[i]);
  }
  fprintf(fp, "e\n");
  fclose(fp);
}

/* ----------------------------------------------------------------------
 * Spline-based Modified Embedded Atom method (MEAM) potential routine.
 *
 * Written by Alexander Stukowski (<alex@stukowski.com>).
 * LLNL-CODE-525797 All rights reserved.
 *
 *
 * Our Preamble Notice
 * A. This notice is required to be provided under our contract with the
 * U.S. Department of Energy (DOE). This work was produced at the
 * Lawrence Livermore National Laboratory under Contract No.
 * DE-AC52-07NA27344 with the DOE.
 *
 * B. Neither the United States Government nor Lawrence Livermore National
 * Security, LLC nor any of their employees, makes any warranty, express or
 * implied, or assumes any liability or responsibility for the accuracy,
 * completeness, or usefulness of any information, apparatus, product, or
 * process disclosed, or represents that its use would not infringe
 * privately-owned rights.
 *
 * C. Also, reference herein to any specific commercial products, process,
 * or services by trade name, trademark, manufacturer or otherwise does not
 * necessarily constitute or imply its endorsement, recommendation, or
 * favoring by the United States Government or Lawrence Livermore National
 * Security, LLC. The views and opinions of authors expressed herein do not
 * necessarily state or reflect those of the United States Government or
 * Lawrence Livermore National Security, LLC, and shall not be used for
 * advertising or product endorsement purposes.
------------------------------------------------------------------------- */


{
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int t, n, x;
    cin >>t;
    while(t--){
        cin >>n >>x;
        vector<int> arr(n);
        for(int i = 0; i < n; i++)
            cin >>arr[i];
        bool flag = false;
        for(int i = 0; i < n; i++)
            if(arr[i] >= x)
                flag = true;
        if(flag)
            cout <<"YES\n";
        else
            cout <<"NO\n";
    }
    return 0;
}

/**
 * Template for Contests
 *
 *  @author Sanjeev
 */
#include <bits/stdc++.h>
using namespace std;

int main()


/**
 * Template for Contests
 *
 *  @author Sanjeev
 */
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int t, n, x

    cin >>t;
    while(t--){
        cin >>n >>x;
        vector<int> arr(n);
        for(int i = 0; i < n; i++)
            cin >>arr[i];
        bool flag = false;
        for(int i = 0; i < n; i++)
            if(arr[i] >= x)
                flag = true;
        if(flag)
            cout <<"YES\n";
        else
            cout <<"NO\n";
    }
    return 0;
}


while(t--){
        cin >>n >>x;
        vector<int> arr(n);
        for(int i = 0; i < n; i++)
            cin >>arr[i];
        bool flag = false;
        for(int i = 0; i < n; i++)
            if(arr[i] >= x)
                flag = true;
        if(flag)
            cout <<"YES\n";
        else
            cout <<"NO\n";
    }

/**
 * Template for Contests
 *
 *  @author Sanjeev
 */
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    int t, n, x;
    cin >>t;


{
        vector<vector<int> > res;
        for (size_t i = 0; i < nums.size(); i++) {
            for (size_t j = i + 1; j < nums.size(); j++) {
                for (size_t k = j + 1; k < nums.size(); k++) {
                    if (nums[i] + nums[j] + nums[k] == target) {
                        res.push_back(vector<int>{nums[i], nums[j], nums[k]});
                    }
                }
            }
        }
        return vector<vector<int> > ();
    }

#include <bits/stdc++.h>

using namespace std;

/*
 * 三数之和
 *
 * 给定一个包含 N 个整数的 nums，判断 nums 中是否存在
 * 三个元素 a, b, c 使得 a + b + c = 0，找出所有序列
 *
 * 给定 nums = {-1, 0, 1, 2, -1, -4}
 *
 * [
 *      [-1, 0, 1]
 *      [-1, -1, 2]
 * ]
 * */

/*
 * 题解
 *
 * 一个新的周末，你再次去现在参加了一个，额，这次不是相亲会，是参加一个街篮比赛
 * ，赛前当然要组队啦，现在要想一个方法找到队友。组队还有一个要求，就是队伍的平
 * 均实力要符合要求，比如菜鸟抱两个大腿，或者有王者带两个弱鸡
 *
 * 当然，类似于两数之和，我们让每个数都相加，看会不会与我们的 target 相等
 *
 * */

class Solution {
public:
    vector<vector<int> > threeSumOne(vector<int>& nums, int target)

    /*
     * 受到两数之和的启发，在凑齐两人之后，我们可以找主持人登记第三个人，而不必在茫茫人海中找，这样时间复杂
     * 度为 n^2
     * */

    /*
     * 再优化
     *
     * 可以先对给定序列进行排序，时间复杂度 nlogn
     * 我后我们选择一个人做 C 位，既然是 C 位，那么我们就需要左右各有一个人
     *
     * 先选择队伍最左边和队伍最右边两个人加上 C 位，算一下总和，如果大于 target 说明
     * 实力强，那么就把右边的人 right-- 一下，重新计算实例值
     *
     * 如果某边选到紧挨着你时，结束
     * */
};

int main()
{
    return 0;
}


#include <bits/stdc++.h>

using namespace std;

/*
 * 三数之和
 *
 * 给定一个包含 N 个整数的 nums，判断 nums 中是否存在
 * 三个元素 a, b, c 使得 a + b + c = 0，找出所有序列
 *
 * 给定 nums = {-1, 0, 1, 2, -1, -4}
 *
 * [
 *      [-1, 0, 1]
 *      [-1, -1, 2]
 * ]
 * */

/*
 * 题解
 *
 * 一个新的周末，你再次去现在参加了一个，额，这次不是相亲会，是参加一个街篮比赛
 * ，赛前当然要组队啦，现在要想一个方法找到队友。组队还有一个要求，就是队伍的平
 * 均实力要符合要求，比如菜鸟抱两个大腿，或者有王者带两个弱鸡
 *
 * 当然，类似于两数之和，我们让每个数都相加，看会不会与我们的 target 相等
 *
 * */

class Solution {
public:
    vector<vector<int> > threeSumOne(vector<int>& nums, int target) {
        vector<vector<int> > res;
        for (size_t i = 0; i < nums.size(); i++) {
            for (size_t j = i + 1; j < nums.size(); j++) {
                for (size_t k = j + 1; k < nums.size(); k++) {
                    if (nums[i] + nums[j] + nums[k] == target) {
                        res.push_back(vector<int>{nums[i], nums[j], nums[k]});
                    }
                }
            }
        }
        return vector<vector

    }

    /*
     * 受到两数之和的启发，在凑齐两人之后，我们可以找主持人登记第三个人，而不必在茫茫人海中找，这样时间复杂
     * 度为 n^2
     * */

    /*
     * 再优化
     *
     * 可以先对给定序列进行排序，时间复杂度 nlogn
     * 我后我们选择一个人做 C 位，既然是 C 位，那么我们就需要左右各有一个人
     *
     * 先选择队伍最左边和队伍最右边两个人加上 C 位，算一下总和，如果大于 target 说明
     * 实力强，那么就把右边的人 right-- 一下，重新计算实例值
     *
     * 如果某边选到紧挨着你时，结束
     * */
};

int main()
{
    return 0;
}


{
	NameHierarchy ret = CxxFunctionDeclName::toNameHierarchy();
	NameElement& last = ret.back();
	last.setSignature(
		last.getSignature().getPrefix(),
		last.getSignature().getPostfix() + L" (" + m_translationUnitFileName + L')');
	return ret;
}

#include "CxxStaticFunctionDeclName.h"

CxxStaticFunctionDeclName::CxxStaticFunctionDeclName(
	std::wstring name,
	std::vector<std::wstring> templateParameterNames,
	std::unique_ptr<CxxTypeName> returnTypeName,
	std::vector<std::unique_ptr<CxxTypeName>> parameterTypeNames,
	std::wstring translationUnitFileName)
	: CxxFunctionDeclName(
		  std::move(name),
		  std::move(templateParameterNames),
		  std::move(returnTypeName),
		  std::move(parameterTypeNames),
		  false,
		  true)
	, m_translationUnitFileName(std::move(translationUnitFileName))
{
}

NameHierarchy CxxStaticFunctionDeclName::toNameHierarchy() const


#include "CxxStaticFunctionDeclName.h"

CxxStaticFunctionDeclName::CxxStaticFunctionDeclName(
	std::wstring name,
	std::vector<std::wstring> templateParameterNames,
	std::unique_ptr<CxxTypeName> returnTypeName,
	std::vector<std::unique_ptr<CxxTypeName>> parameterTypeNames,
	std::wstring translationUnitFileName)
	: CxxFunctionDeclName(
		  std::move(name),
		  std::move(templateParameterNames),
		  std::move(returnTypeName),
		  std::move(parameterTypeNames),
		  false,
		  true)
	, m_translationUnitFileName(std::move(translationUnitFileName))
{
}

NameHierarchy CxxStaticFunctionDeclName::toNameHierarchy() const
{
	NameHierarchy ret = Cx

	NameElement& last = ret.back();
	last.setSignature(
		last.getSignature().getPrefix(),
		last.getSignature().getPostfix() + L" (" + m_translationUnitFileName + L')');
	return ret;
}


{//some inits

    //YOU HAVE TO PROVIDE THE SAMPLES....

	sound1.load("/Users/mickgrierson/Documents/audio/68373__juskiddink__Cello_open_string_bowed.wav");//load in your samples. Provide the full path to a wav file.

	printf("Summary:\n%s", sound1.getSummary());//get info on samples if you like.
}

#include "maximilian.h"

//this tutorial explains how to use the maxiEnv

maxiSample sound1;

maxiOsc timer,snarePhase; //and a timer

maxiEnv envelope;//this is going to be an envelope

int currentCount,lastCount,playHead,

sequence[16]={1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0}; //This is the sequence for the kick

int sampleTrigger;

double sampleOut;

void setup()

void play(double *output) {//this is where the magic happens. Very slow magic.

	currentCount=(int)timer.phasor(8);//this sets up a metronome that ticks 8 times a second

	if (lastCount!=currentCount) {//if we have a new timer int this sample, play the sound

		sampleTrigger=sequence[playHead%16];
		playHead++;//iterate the playhead
		lastCount=0;//reset the metrotest

	}

	//the envelope we're using here is an AR envelope.
	//It has an input (which in this case is a sound)
	//It has an attack coefficient, a hold val (in samples)
	//and a release coefficient. Finally, it has a trigger input.
	//If you stick a 1 in the trigger input, it retriggers the envelope
	sampleOut=envelope.ar(sound1.play(1.), 0.1, 0.9999, 1, sampleTrigger); //

	output[0]=sampleOut;//left channel
	output[1]=sampleOut;//right channel

	sampleTrigger = 0;//set trigger to 0 at the end of each sample to guarantee retriggering.

}


#include "maximilian.h"

//this tutorial explains how to use the maxiEnv

maxiSample sound1;

maxiOsc timer,snarePhase; //and a timer

maxiEnv envelope;//this is going to be an envelope

int currentCount,lastCount,playHead,

sequence[16]={1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0}; //This is the sequence for the kick

int sampleTrigger;

double sampleOut;

void setup() {//some inits

    //YOU HAVE TO PROVIDE THE SAMPLES....

	sound1.load("/Users/mickgrierson/Documents/audio/68373__juskiddink__Cello_open_string_bowed.wav");//load in your samples. Provide the full path to a wav file.

	printf("Summary:\n%s", sound1.getSummary());//get info on samples if you like.
}

void play(double *output) {//this is where the magic happens. Very slow magic.

	currentCount=(int)t

//this sets up a metronome that ticks 8 times a second

	if (lastCount!=currentCount) {//if we have a new timer int this sample, play the sound

		sampleTrigger=sequence[playHead%16];
		playHead++;//iterate the playhead
		lastCount=0;//reset the metrotest

	}

	//the envelope we're using here is an AR envelope.
	//It has an input (which in this case is a sound)
	//It has an attack coefficient, a hold val (in samples)
	//and a release coefficient. Finally, it has a trigger input.
	//If you stick a 1 in the trigger input, it retriggers the envelope
	sampleOut=envelope.ar(sound1.play(1.), 0.1, 0.9999, 1, sampleTrigger); //

	output[0]=sampleOut;//left channel
	output[1]=sampleOut;//right channel

	sampleTrigger = 0;//set trigger to 0 at the end of each sample to guarantee retriggering.

}


{
  OpenANN::RandomNumberGenerator rng;
  rng.seed(6);
}

#include "ConvolutionalTestCase.h"
#include "FiniteDifferences.h"
#include "LayerAdapter.h"
#include <OpenANN/layers/Convolutional.h>
#include <OpenANN/util/Random.h>

void ConvolutionalTestCase::run()
{
  RUN(ConvolutionalTestCase, convolutional);
  RUN(ConvolutionalTestCase, convolutionalGradient);
  RUN(ConvolutionalTestCase, convolutionalInputGradient);
  RUN(ConvolutionalTestCase, regularization);
  RUN(ConvolutionalTestCase, convolutionalGradientWithOddKernel);
}

void ConvolutionalTestCase::setUp()

void ConvolutionalTestCase::convolutional()
{
  OpenANN::OutputInfo info;
  info.dimensions.push_back(2);
  info.dimensions.push_back(4);
  info.dimensions.push_back(4);
  OpenANN::Convolutional layer(info, 2, 3, 3, false, OpenANN::TANH, 0.05, OpenANN::Regularization());
  std::vector<double*> pp;
  std::vector<double*> pdp;
  OpenANN::OutputInfo info2 = layer.initialize(pp, pdp);
  ASSERT_EQUALS(info2.dimensions.size(), 3);
  ASSERT_EQUALS(info2.dimensions[0], 2);
  ASSERT_EQUALS(info2.dimensions[1], 2);
  ASSERT_EQUALS(info2.dimensions[2], 2);

  for(std::vector<double*>::iterator it = pp.begin(); it != pp.end(); ++it)
    **it = 0.01;
  layer.updatedParameters();

  Eigen::MatrixXd x(1, info.outputs());
  x.fill(1.0);
  Eigen::MatrixXd* y;
  layer.forwardPropagate(&x, y, false);
  ASSERT_EQUALS_DELTA((*y)(0), tanh(0.18), 1e-5);
  ASSERT_EQUALS_DELTA((*y)(1), tanh(0.18), 1e-5);
  ASSERT_EQUALS_DELTA((*y)(2), tanh(0.18), 1e-5);
  ASSERT_EQUALS_DELTA((*y)(3), tanh(0.18), 1e-5);
  ASSERT_EQUALS_DELTA((*y)(4), tanh(0.18), 1e-5);
  ASSERT_EQUALS_DELTA((*y)(5), tanh(0.18), 1e-5);
  ASSERT_EQUALS_DELTA((*y)(6), tanh(0.18), 1e-5);
  ASSERT_EQUALS_DELTA((*y)(7), tanh(0.18), 1e-5);
}

void ConvolutionalTestCase::convolutionalGradient()
{
  OpenANN::OutputInfo info;
  info.dimensions.push_back(3);
  info.dimensions.push_back(5);
  info.dimensions.push_back(5);
  OpenANN::Convolutional layer(info, 2, 3, 3, true, OpenANN::LINEAR, 0.05, OpenANN::Regularization());
  LayerAdapter opt(layer, info);

  Eigen::MatrixXd X = Eigen::MatrixXd::Random(2, 3*5*5);
  Eigen::MatrixXd Y = Eigen::MatrixXd::Random(2, 2*3*3);
  std::vector<int> indices;
  indices.push_back(0);
  indices.push_back(1);
  opt.trainingSet(X, Y);
  Eigen::VectorXd gradient = opt.gradient(indices.begin(), indices.end());
  Eigen::VectorXd estimatedGradient = OpenANN::FiniteDifferences::parameterGradient(
      indices.begin(), indices.end(), opt);
  for(int i = 0; i < gradient.rows(); i++)
    ASSERT_EQUALS_DELTA(gradient(i), estimatedGradient(i), 1e-10);
}

void ConvolutionalTestCase::convolutionalInputGradient()
{
  OpenANN::OutputInfo info;
  info.dimensions.push_back(3);
  info.dimensions.push_back(5);
  info.dimensions.push_back(5);
  OpenANN::Convolutional layer(info, 2, 3, 3, true, OpenANN::LINEAR, 0.05, OpenANN::Regularization());
  LayerAdapter opt(layer, info);

  Eigen::MatrixXd X = Eigen::MatrixXd::Random(2, 3*5*5);
  Eigen::MatrixXd Y = Eigen::MatrixXd::Random(2, 2*3*3);
  std::vector<int> indices;
  indices.push_back(0);
  indices.push_back(1);
  opt.trainingSet(X, Y);
  Eigen::MatrixXd gradient = opt.inputGradient();
  ASSERT_EQUALS(gradient.rows(), 2);
  Eigen::MatrixXd estimatedGradient = OpenANN::FiniteDifferences::inputGradient(X, Y,
                                                                       opt);
  ASSERT_EQUALS(estimatedGradient.rows(), 2);
  for(int j = 0; j < gradient.rows(); j++)
    for(int i = 0; i < gradient.cols(); i++)
      ASSERT_EQUALS_DELTA(gradient(j, i), estimatedGradient(j, i), 1e-10);
}

void ConvolutionalTestCase::regularization()
{
  OpenANN::OutputInfo info;
  info.dimensions.push_back(3);
  info.dimensions.push_back(5);
  info.dimensions.push_back(5);
  OpenANN::Convolutional layer(info, 2, 3, 3, true, OpenANN::LINEAR, 0.05,
                               OpenANN::Regularization(0.1, 0.1));
  LayerAdapter opt(layer, info);

  Eigen::MatrixXd X = Eigen::MatrixXd::Random(1, 3*5*5);
  Eigen::MatrixXd Y = Eigen::MatrixXd::Random(1, 2*3*3);
  opt.trainingSet(X, Y);
  Eigen::VectorXd gradient = opt.gradient(0);
  Eigen::VectorXd estimatedGradient = OpenANN::FiniteDifferences::parameterGradient(0, opt);
  for(int i = 0; i < gradient.rows(); i++)
    ASSERT_EQUALS_DELTA(gradient(i), estimatedGradient(i), 1e-10);
}

void ConvolutionalTestCase::convolutionalGradientWithOddKernel()
{
  OpenANN::OutputInfo info;
  info.dimensions.push_back(3);
  info.dimensions.push_back(4);
  info.dimensions.push_back(4);
  OpenANN::Convolutional layer(info, 2, 2, 2, true, OpenANN::LINEAR, 0.05, OpenANN::Regularization());
  LayerAdapter opt(layer, info);

  Eigen::MatrixXd X = Eigen::MatrixXd::Random(2, 3*4*4);
  Eigen::MatrixXd Y = Eigen::MatrixXd::Random(2, 2*2*2);
  std::vector<int> indices;
  indices.push_back(0);
  indices.push_back(1);
  opt.trainingSet(X, Y);
  Eigen::VectorXd gradient = opt.gradient(indices.begin(), indices.end());
  Eigen::VectorXd estimatedGradient = OpenANN::FiniteDifferences::parameterGradient(
      indices.begin(), indices.end(), opt);
  for(int i = 0; i < gradient.rows(); i++)
    ASSERT_EQUALS_DELTA(gradient(i), estimatedGradient(i), 1e-10);
}


MatrixXd Y = Eigen::MatrixXd::Random(1, 2*3*3);

#include "ConvolutionalTestCase.h"
#include "FiniteDifferences.h"
#include "LayerAdapter.h"
#include <OpenANN/layers/Convolutional.h>
#include <OpenANN/util/Random.h>

void ConvolutionalTestCase::run()
{
  RUN(ConvolutionalTestCase, convolutional);
  RUN(ConvolutionalTestCase, convolutionalGradient);
  RUN(ConvolutionalTestCase, convolutionalInputGradient);
  RUN(ConvolutionalTestCase, regularization);
  RUN(ConvolutionalTestCase, convolutionalGradientWithOddKernel);
}

void ConvolutionalTestCase::setUp()
{
  OpenANN::RandomNumberGenerator rng;
  rng.seed(6);
}

void ConvolutionalTestCase::convolutional()
{
  OpenANN::OutputInfo info;
  info.dimensions.push_back(2);
  info.dimensions.push_back(4);
  info.dimensions.push_back(4);
  OpenANN::Convolutional layer(info, 2, 3, 3, false, OpenANN::TANH, 0.05, OpenANN::Regularization());
  std::vector<double*> pp;
  std::vector<double*> pdp;
  OpenANN::OutputInfo info2 = layer.initialize(pp, pdp);
  ASSERT_EQUALS(info2.dimensions.size(), 3);
  ASSERT_EQUALS(info2.dimensions[0], 2);
  ASSERT_EQUALS(info2.dimensions[1], 2);
  ASSERT_EQUALS(info2.dimensions[2], 2);

  for(std::vector<double*>::iterator it = pp.begin(); it != pp.end(); ++it)
    **it = 0.01;
  layer.updatedParameters();

  Eigen::MatrixXd x(1, info.outputs());
  x.fill(1.0);
  Eigen::MatrixXd* y;
  layer.forwardPropagate(&x, y, false);
  ASSERT_EQUALS_DELTA((*y)(0), tanh(0.18), 1e-5);
  ASSERT_EQUALS_DELTA((*y)(1), tanh(0.18), 1e-5);
  ASSERT_EQUALS_DELTA((*y)(2), tanh(0.18), 1e-5);
  ASSERT_EQUALS_DELTA((*y)(3), tanh(0.18), 1e-5);
  ASSERT_EQUALS_DELTA((*y)(4), tanh(0.18), 1e-5);
  ASSERT_EQUALS_DELTA((*y)(5), tanh(0.18), 1e-5);
  ASSERT_EQUALS_DELTA((*y)(6), tanh(0.18), 1e-5);
  ASSERT_EQUALS_DELTA((*y)(7), tanh(0.18), 1e-5);
}

void ConvolutionalTestCase::convolutionalGradient()
{
  OpenANN::OutputInfo info;
  info.dimensions.push_back(3);
  info.dimensions.push_back(5);
  info.dimensions.push_back(5);
  OpenANN::Convolutional layer(info, 2, 3, 3, true, OpenANN::LINEAR, 0.05, OpenANN::Regularization());
  LayerAdapter opt(layer, info);

  Eigen::MatrixXd X = Eigen::MatrixXd::Random(2, 3*5*5);
  Eigen::MatrixXd Y = Eigen::MatrixXd::Random(2, 2*3*3);
  std::vector<int> indices;
  indices.push_back(0);
  indices.push_back(1);
  opt.trainingSet(X, Y);
  Eigen::VectorXd gradient = opt.gradient(indices.begin(), indices.end());
  Eigen::VectorXd estimatedGradient = OpenANN::FiniteDifferences::parameterGradient(
      indices.begin(), indices.end(), opt);
  for(int i = 0; i < gradient.rows(); i++)
    ASSERT_EQUALS_DELTA(gradient(i), estimatedGradient(i), 1e-10);
}

void ConvolutionalTestCase::convolutionalInputGradient()
{
  OpenANN::OutputInfo info;
  info.dimensions.push_back(3);
  info.dimensions.push_back(5);
  info.dimensions.push_back(5);
  OpenANN::Convolutional layer(info, 2, 3, 3, true, OpenANN::LINEAR, 0.05, OpenANN::Regularization());
  LayerAdapter opt(layer, info);

  Eigen::MatrixXd X = Eigen::MatrixXd::Random(2, 3*5*5);
  Eigen::MatrixXd Y = Eigen::MatrixXd::Random(2, 2*3*3);
  std::vector<int> indices;
  indices.push_back(0);
  indices.push_back(1);
  opt.trainingSet(X, Y);
  Eigen::MatrixXd gradient = opt.inputGradient();
  ASSERT_EQUALS(gradient.rows(), 2);
  Eigen::MatrixXd estimatedGradient = OpenANN::FiniteDifferences::inputGradient(X, Y,
                                                                       opt);
  ASSERT_EQUALS(estimatedGradient.rows(), 2);
  for(int j = 0; j < gradient.rows(); j++)
    for(int i = 0; i < gradient.cols(); i++)
      ASSERT_EQUALS_DELTA(gradient(j, i), estimatedGradient(j, i), 1e-10);
}

void ConvolutionalTestCase::regularization()
{
  OpenANN::OutputInfo info;
  info.dimensions.push_back(3);
  info.dimensions.push_back(5);
  info.dimensions.push_back(5);
  OpenANN::Convolutional layer(info, 2, 3, 3, true, OpenANN::LINEAR, 0.05,
                               OpenANN::Regularization(0.1, 0.1));
  LayerAdapter opt(layer, info);

  Eigen::MatrixXd X = Eigen::MatrixXd::Random(1, 3*5*5);
  Eigen::

  opt.trainingSet(X, Y);
  Eigen::VectorXd gradient = opt.gradient(0);
  Eigen::VectorXd estimatedGradient = OpenANN::FiniteDifferences::parameterGradient(0, opt);
  for(int i = 0; i < gradient.rows(); i++)
    ASSERT_EQUALS_DELTA(gradient(i), estimatedGradient(i), 1e-10);
}

void ConvolutionalTestCase::convolutionalGradientWithOddKernel()
{
  OpenANN::OutputInfo info;
  info.dimensions.push_back(3);
  info.dimensions.push_back(4);
  info.dimensions.push_back(4);
  OpenANN::Convolutional layer(info, 2, 2, 2, true, OpenANN::LINEAR, 0.05, OpenANN::Regularization());
  LayerAdapter opt(layer, info);

  Eigen::MatrixXd X = Eigen::MatrixXd::Random(2, 3*4*4);
  Eigen::MatrixXd Y = Eigen::MatrixXd::Random(2, 2*2*2);
  std::vector<int> indices;
  indices.push_back(0);
  indices.push_back(1);
  opt.trainingSet(X, Y);
  Eigen::VectorXd gradient = opt.gradient(indices.begin(), indices.end());
  Eigen::VectorXd estimatedGradient = OpenANN::FiniteDifferences::parameterGradient(
      indices.begin(), indices.end(), opt);
  for(int i = 0; i < gradient.rows(); i++)
    ASSERT_EQUALS_DELTA(gradient(i), estimatedGradient(i), 1e-10);
}


// server.cpp

#include <ctime>
#include <iostream>
#include <string>
#include <boost/array.hpp>
#include <boost/bind/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <asio.hpp>

using asio::ip::udp;

std::string make_daytime_string()
{
  using namespace std; // For time_t, time and ctime;
  time_t now = time(0);
  return ctime(&now);
}

class udp_server
{
public:
  udp_server(asio::io_context& io_context)
    : socket_(io_context, udp::endpoint(udp::v4(), 13))
  {
    start_receive();
  }

private:
  void start_receive()
  {
    socket_.async_receive_from(
        asio::buffer(recv_buffer_), remote_endpoint_,
        boost::bind(&udp_server::handle_receive, this,
          asio::placeholders::error,
          asio::placeholders::bytes_transferred));
  }

  void handle_receive(const asio::error_code& error,
      std::size_t /*bytes_transferred*/)
  {
    if (!error)
    {
      boost::shared_ptr<std::string> message(
          new std::string(make_daytime_string()));

      socket_.async_send_to(asio::buffer(*message), remote_endpoint_,
          boost::bind(&udp_server::handle_send, this, message,
            asio::placeholders::error,
            asio::placeholders::bytes_transferred));

      start_receive();
    }
  }

  void handle_send(boost::shared_ptr<std::string> /*message*/,
      const asio::error_code& /*error*/,
      std::size_t /*bytes_transferred*/)

  udp::socket socket_;
  udp::endpoint remote_endpoint_;
  boost::array<char, 1> recv_buffer_;
};

int main()
{
  try
  {
    asio::io_context io_context;
    udp_server server(io_context);
    io_context.run();
  }
  catch (std::exception& e)
  {
    std::cerr << e.what() << std::endl;
  }

  return 0;
}


// server.cpp

#include <ctime>
#include <iostream>
#include <string>
#include <boost/array.hpp>
#include <boost/bind/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <asio.hpp>

using asio::ip::udp;

std::string make_daytime_string()
{
  using namespace std; // For time_t, time and ctime;
  time_t now = time(0);
  return ctime(&now);
}

class udp_server
{
public:
  udp_server(asio::io_context& io_context)
    : socket_(io_context, udp::endpoint(udp::v4(), 13))
  {
    start_re

  }

private:
  void start_receive()
  {
    socket_.async_receive_from(
        asio::buffer(recv_buffer_), remote_endpoint_,
        boost::bind(&udp_server::handle_receive, this,
          asio::placeholders::error,
          asio::placeholders::bytes_transferred));
  }

  void handle_receive(const asio::error_code& error,
      std::size_t /*bytes_transferred*/)
  {
    if (!error)
    {
      boost::shared_ptr<std::string> message(
          new std::string(make_daytime_string()));

      socket_.async_send_to(asio::buffer(*message), remote_endpoint_,
          boost::bind(&udp_server::handle_send, this, message,
            asio::placeholders::error,
            asio::placeholders::bytes_transferred));

      start_receive();
    }
  }

  void handle_send(boost::shared_ptr<std::string> /*message*/,
      const asio::error_code& /*error*/,
      std::size_t /*bytes_transferred*/)
  {
  }

  udp::socket socket_;
  udp::endpoint remote_endpoint_;
  boost::array<char, 1> recv_buffer_;
};

int main()
{
  try
  {
    asio::io_context io_context;
    udp_server server(io_context);
    io_context.run();
  }
  catch (std::exception& e)
  {
    std::cerr << e.what() << std::endl;
  }

  return 0;
}


try
  {
    asio::io_context io_context;
    udp_server server(io_context);
    io_context.run();
  }
  catch (std::exception& e)
  {
    std::cerr << e.what() << std::endl;
  }

// server.cpp

#include <ctime>
#include <iostream>
#include <string>
#include <boost/array.hpp>
#include <boost/bind/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <asio.hpp>

using asio::ip::udp;

std::string make_daytime_string()
{
  using namespace std; // For time_t, time and ctime;
  time_t now = time(0);
  return ctime(&now);
}

class udp_server
{
public:
  udp_server(asio::io_context& io_context)
    : socket_(io_context, udp::endpoint(udp::v4(), 13))
  {
    start_receive();
  }

private:
  void start_receive()
  {
    socket_.async_receive_from(
        asio::buffer(recv_buffer_), remote_endpoint_,
        boost::bind(&udp_server::handle_receive, this,
          asio::placeholders::error,
          asio::placeholders::bytes_transferred));
  }

  void handle_receive(const asio::error_code& error,
      std::size_t /*bytes_transferred*/)
  {
    if (!error)
    {
      boost::shared_ptr<std::string> message(
          new std::string(make_daytime_string()));

      socket_.async_send_to(asio::buffer(*message), remote_endpoint_,
          boost::bind(&udp_server::handle_send, this, message,
            asio::placeholders::error,
            asio::placeholders::bytes_transferred));

      start_receive();
    }
  }

  void handle_send(boost::shared_ptr<std::string> /*message*/,
      const asio::error_code& /*error*/,
      std::size_t /*bytes_transferred*/)
  {
  }

  udp::socket socket_;
  udp::endpoint remote_endpoint_;
  boost::array<char, 1> recv_buffer_;
};

int main()
{


#include "confirm_page.hpp"

#include <algorithm>
#include <filesystem>
#include <string>

#include "fs.hpp"
#include "main_frame.hpp"
#include "utils.hpp"

namespace i18n = brls::i18n;
using namespace i18n::literals;
ConfirmPage::ConfirmPage(brls::StagedAppletFrame* frame, const std::string& text, bool done, bool reboot, bool erista) : done(done), reboot(reboot), erista(erista)
{
    this->button = (new brls::Button(brls::ButtonStyle::REGULAR))->setLabel(done ? "menus/common/back"_i18n : "menus/common/continue"_i18n);
    this->button->setParent(this);
    this->button->getClickEvent()->subscribe([frame, this](View* view) {
        if (!frame->isLastStage()) {
            frame->nextStage();
        }
        else if (this->done) {
            brls::Application::pushView(new MainFrame());
        }
        else if (this->reboot) {
            if (this->erista) {
                util::rebootToPayload(RCM_PAYLOAD_PATH);
            }
            else {
                if (std::filesystem::exists(UPDATE_BIN_PATH)) {
                    fs::copyFile(UPDATE_BIN_PATH, MARIKO_PAYLOAD_PATH_TEMP);
                }
                else {
                    fs::copyFile(REBOOT_PAYLOAD_PATH, MARIKO_PAYLOAD_PATH_TEMP);
                }
                fs::copyFile(RCM_PAYLOAD_PATH, MARIKO_PAYLOAD_PATH);
                util::shutDown(true);
            }
        }
    });

    this->label = new brls::Label(brls::LabelStyle::DIALOG, text, true);
    this->label->setHorizontalAlign(NVG_ALIGN_CENTER);
    this->label->setParent(this);

    if (this->done || this->reboot)
        this->registerAction("", brls::Key::B, [this] { return true; });
}

void ConfirmPage::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
{
    if (!this->done) {
        auto end = std::chrono::high_resolution_clock::now();
        auto missing = std::max(1l - std::chrono::duration_cast<std::chrono::seconds>(end - start).count(), 0l);
        auto text = std::string("menus/common/continue"_i18n);
        if (missing > 0) {
            this->button->setLabel(text + " (" + std::to_string(missing) + ")");
            this->button->setState(brls::ButtonState::DISABLED);
        }
        else {
            this->button->setLabel(text);
            this->button->setState(brls::ButtonState::ENABLED);
        }
        this->button->invalidate();
    }
    else {
        this->button->setState(brls::ButtonState::ENABLED);
    }
    this->label->frame(ctx);
    this->button->frame(ctx);
}

brls::View* ConfirmPage::getDefaultFocus()

void ConfirmPage::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
{
    this->label->setWidth(this->width);
    this->label->invalidate(true);
    this->label->setBoundaries(
        this->x + this->width / 2 - this->label->getWidth() / 2,
        this->y + (this->height - this->label->getHeight() - this->y - style->CrashFrame.buttonHeight) / 2,
        this->label->getWidth(),
        this->label->getHeight());

    this->button->setBoundaries(
        this->x + this->width / 2 - style->CrashFrame.buttonWidth / 2,
        this->y + (this->height - style->CrashFrame.buttonHeight * 3),
        style->CrashFrame.buttonWidth,
        style->CrashFrame.buttonHeight);
    this->button->invalidate();

    start = std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(150);
}

ConfirmPage::~ConfirmPage()
{
    delete this->label;
    delete this->button;
}


#include "confirm_page.hpp"

#include <algorithm>
#include <filesystem>
#include <string>

#include "fs.hpp"
#include "main_frame.hpp"
#include "utils.hpp"

namespace i18n = brls::i18n;
using namespace i18n::literals;
ConfirmPage::ConfirmPage(brls::StagedAppletFrame* frame, const std::string& text, bool done, bool reboot, bool erista) : done(done), reboot(reboot), erista(erista)
{
    this->button = (new brls::Button(brls::ButtonStyle::REGULAR))->setLabel(done ? "menus/common/back"_i18n : "menus/common/continue"_i18n);
    this->button->setParent(this);
    this->button->getClickEvent()->subscribe([frame, this](View* view) {
        if (!frame->isLastStage()) {
            frame->nextStage();
        }
        else if (this->done) {
            brls::Application::pushView(new MainFrame());
        }
        else if (this->reboot) {
            if (this->erista) {
                util::rebootToPayload(RCM_PAYLOAD_PATH);
            }
            else {
                if (std::filesystem::exists(UPDATE_BIN_PATH)) {
                    fs::copyFile(UPDATE_BIN_PATH, MARIKO_PAYLOAD_PATH_TEMP);
                }
                else {
                    fs::copyFile(REBOOT_PAYLOAD_PATH, MARIKO_PAYLOAD_PATH_TEMP);
                }
                fs::copyFile(RCM_PAYLOAD_PATH, MARIKO_PAYLOAD_PATH);
                util::shutDown(true);
            }
        }
    });

    this->label = new brls::Label(brls::LabelStyle::DIALOG, text, true);
    this->label->setHorizontalAlign(NVG_ALIGN_CENTER);
    this->label->setParent(this);

    if (this->done || this->reboot)
        this->registerAction("", brls::Key::B, [this] { return true; });
}

void ConfirmPage::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
{
    if (!this->done) {
        auto end = std::chrono::high_resolution_clock::now();
        auto missing = std::max(1l - std::chrono::duration_cast<std::chrono::seconds>(end - start).count(), 0l);
        auto text = std::string("menus/common/continue"_i18n);
        if (missing > 0) {
            this->button->setLabel(text + " (" + std::to_string(missing) + ")");
            this->button->setState(brls::ButtonState::DISABLED);
        }
        else {
            this->button->setLabel(text);
            this->button->setState(brls::ButtonState::ENABLED);
        }
        this->button->invalidate();
    }
    else {
        this->button->setState(brls::ButtonState::ENABLED);
    }
    this->label->frame(ctx);
    this->button->frame(ctx);
}

brls::View* ConfirmPage::getDefaultFocus()
{
    return this->button;
}

void ConfirmPage::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
{
    this->label->setWidth(this->width);
    this->label->invalidate(true);
    this->label->setBoundaries(
        this->x + this->width / 2 - this->label->getWidth() / 2,
        this->y + (this->height - this->label->getHeight() - this->y - style->CrashFrame.buttonHeight) / 2,
        this->label->getWidth(),
        this->label->getHeight());

    this->button->setBoundaries(
        this->x + this->width / 2 - style->CrashFrame.buttonWidth / 2,
        this->y + (this->height - style->CrashFrame.buttonHeight * 3),
        style->CrashFrame.buttonWidth,
        style->CrashFrame.buttonHeight);
    this->button->invalidate();

    start = std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(150);
}

ConfirmPage::~ConfirmPage()
{
    del


{
  FILE* fstream = NULL;
  char buff[1024];
  bool success = true;
  memset(buff, 0, sizeof(buff));

  if (NULL == (fstream = popen(cmd.c_str(), "r"))) {
    INVARIANT(0);
    return false;
  }

  memset(buff, 0x00, sizeof(buff));
  while (NULL != fgets(buff, sizeof(buff), fstream)) {
    if (buff[0] != '\0') {
      LOG(INFO) << std::string(buff);
      memset(buff, 0x00, sizeof(buff));
      success = false;
    }
  }
  pclose(fstream);
  return success;
}

// Please refer to the license text that comes with this tendis open source
// project for additional information.

#include <stdlib.h>

#include <memory>
#include <utility>
#include <thread>  // NOLINT
#include <string>
#include <vector>
#include <algorithm>

#include "gtest/gtest.h"
#include "glog/logging.h"

#include "tendisplus/server/server_entry.h"
#include "tendisplus/server/index_manager.h"
#include "tendisplus/server/segment_manager.h"
#include "tendisplus/commands/command.h"
#include "tendisplus/network/network.h"
#include "tendisplus/utils/test_util.h"
#include "tendisplus/utils/scopeguard.h"
#include "tendisplus/utils/sync_point.h"
#include "tendisplus/utils/invariant.h"

namespace tendisplus {

const char* master1_dir = "restoretest_master1";
const char* master2_dir = "restoretest_master2";
const char* slave1_dir = "restoretest_slave1";
uint32_t master1_port = 12001;
uint32_t master2_port = 12002;
uint32_t slave1_port = 12003;

AllKeys initData(std::shared_ptr<ServerEntry> server,
                 uint32_t count,
                 const char* key_suffix) {
  auto ctx1 = std::make_shared<asio::io_context>();
  auto sess1 = makeSession(server, ctx1);
  WorkLoad work(server, sess1);
  work.init();

  AllKeys all_keys;

  auto kv_keys = work.writeWork(RecordType::RT_KV, count, 0, true, key_suffix);
  all_keys.emplace_back(kv_keys);

  auto list_keys =
    work.writeWork(RecordType::RT_LIST_META, count, 2, true, key_suffix);
  all_keys.emplace_back(list_keys);

  auto hash_keys =
    work.writeWork(RecordType::RT_HASH_META, count, 2, true, key_suffix);
  all_keys.emplace_back(hash_keys);

  auto set_keys =
    work.writeWork(RecordType::RT_SET_META, count, 2, true, key_suffix);
  all_keys.emplace_back(set_keys);

  auto zset_keys =
    work.writeWork(RecordType::RT_ZSET_META, count, 2, true, key_suffix);
  all_keys.emplace_back(zset_keys);

  return all_keys;
}

AllKeys initKvData(const std::shared_ptr<ServerEntry>& server,
                   uint32_t count,
                   const char* key_suffix) {
  auto ctx1 = std::make_shared<asio::io_context>();
  auto sess1 = makeSession(server, ctx1);
  WorkLoad work(server, sess1);
  work.init();

  AllKeys all_keys;

  auto kv_keys = work.writeWork(RecordType::RT_KV, count, 0, true, key_suffix);
  all_keys.emplace_back(kv_keys);

  return std::move(all_keys);
}

void addOneKeyEveryKvstore(const std::shared_ptr<ServerEntry>& server,
                           const char* key) {
  for (size_t i = 0; i < server->getKVStoreCount(); i++) {
    auto kvstore = server->getStores()[i];
    auto eTxn1 = kvstore->createTransaction(nullptr);
    EXPECT_EQ(eTxn1.ok(), true);
    std::unique_ptr<Transaction> txn1 = std::move(eTxn1.value());

    Status s =
      kvstore->setKV(Record(RecordKey(0, 0, RecordType::RT_KV, key, ""),
                            RecordValue("txn1", RecordType::RT_KV, -1)),
                     txn1.get());
    EXPECT_EQ(s.ok(), true);

    Expected<uint64_t> exptCommitId = txn1->commit();
    EXPECT_TRUE(exptCommitId.ok());
  }
}

void backup(const std::shared_ptr<ServerEntry>& server, const string& mode) {
  auto ctx = std::make_shared<asio::io_context>();
  auto sess = makeSession(server, ctx);

  // clear data
  const char* dir = "./back_test";
  if (filesystem::exists(dir)) {
    filesystem::remove_all(dir);
  }
  filesystem::create_directory(dir);

  std::vector<std::string> args;
  args.push_back("backup");
  args.push_back(dir);
  args.push_back(mode);
  sess->setArgs(args);
  auto expect = Command::runSessionCmd(sess.get());
  EXPECT_TRUE(expect.ok());
}

void restoreBackup(const std::shared_ptr<ServerEntry>& server) {
  auto ctx = std::make_shared<asio::io_context>();
  auto sess = makeSession(server, ctx);

  std::vector<std::string> args;
  args.push_back("restorebackup");
  args.push_back("all");
  args.push_back("./back_test");
  args.push_back("force");
  sess->setArgs(args);
  auto expect = Command::runSessionCmd(sess.get());
  EXPECT_TRUE(expect.ok());
}

void flushBinlog(const std::shared_ptr<ServerEntry>& server) {
  auto ctx = std::make_shared<asio::io_context>();
  auto sess = makeSession(server, ctx);

  for (size_t i = 0; i < server->getKVStoreCount(); i++) {
    std::vector<std::string> args;
    args.push_back("binlogflush");
    args.push_back(std::to_string(i));
    sess->setArgs(args);
    auto expect = Command::runSessionCmd(sess.get());
    EXPECT_TRUE(expect.ok());
  }
}

#ifdef _WIN32
#define popen _popen
#define pclose _pclose
#endif

bool runShell(const std::string& cmd)

void restoreBinlog(const string& src_binlog_dir,
                   const std::shared_ptr<ServerEntry>& server,
                   uint64_t end_ts = UINT64_MAX) {
  for (size_t i = 0; i < server->getKVStoreCount(); i++) {
    auto kvstore = server->getStores()[i];
    uint64_t binglogPos = kvstore->getHighestBinlogId();

    std::string subpath =
      "./" + src_binlog_dir + "/dump/" + std::to_string(i) + "/";
    std::vector<std::string> loglist;
    for (auto& p : filesystem::recursive_directory_iterator(subpath)) {
      const filesystem::path& path = p.path();
      if (!filesystem::is_regular_file(p)) {
        LOG(INFO) << "maxDumpFileSeq ignore:" << p.path();
        continue;
      }
      // assert path with dir prefix
#ifndef _WIN32
      INVARIANT(path.string().find(subpath) == 0);
#endif
      std::string relative = path.string().erase(0, subpath.size());
      if (relative.substr(0, 6) != "binlog") {
        LOG(INFO) << "maxDumpFileSeq ignore:" << relative;
        continue;
      }
      LOG(INFO) << "binlog file:" << p.path();
      loglist.push_back(p.path().u8string());
    }
    if (loglist.size() <= 0) {
      continue;
    }
    std::sort(loglist.begin(), loglist.end());
    for (size_t j = 0; j < loglist.size(); ++j) {
      std::string cmd = "./build/bin/binlog_tool";
      cmd += " --logfile=" + loglist[j];
      cmd += " --mode=base64";
      cmd += " --start-position=" + std::to_string(binglogPos + 1);
      cmd += " --end-datetime=" + std::to_string(end_ts);
      cmd += "| ./bin/redis-cli --csv -p " + std::to_string(master2_port);
      cmd += "| grep -v OK";
      LOG(INFO) << cmd;

      EXPECT_TRUE(runShell(cmd));
    }
  }
}

void waitBinlogDump(const std::shared_ptr<ServerEntry>& server) {
  INVARIANT(server->getParams()->maxBinlogKeepNum == 1);
  for (size_t i = 0; i < server->getKVStoreCount(); i++) {
    auto kvstore = server->getStores()[i];

    while (true) {
      auto ptxn = kvstore->createTransaction(nullptr);
      EXPECT_TRUE(ptxn.ok());
      std::unique_ptr<Transaction> txn = std::move(ptxn.value());
      uint64_t minBinlogId = 0;
      auto expBinlogidMin = RepllogCursorV2::getMinBinlogId(txn.get());
      if (expBinlogidMin.status().code() == ErrorCodes::ERR_EXHAUST) {
        minBinlogId = 0;
      } else {
        EXPECT_TRUE(expBinlogidMin.ok());
        minBinlogId = expBinlogidMin.value();
      }

      uint64_t maxBinlogId = 0;
      auto expBinlogidMax = RepllogCursorV2::getMaxBinlogId(txn.get());
      if (expBinlogidMax.status().code() == ErrorCodes::ERR_EXHAUST) {
        maxBinlogId = 0;
      } else {
        EXPECT_TRUE(expBinlogidMin.ok());
        maxBinlogId = expBinlogidMax.value();
      }

      // wait only one binlog left in rocksdb
      if (minBinlogId == maxBinlogId) {
        break;
      } else {
        std::this_thread::sleep_for(10ms);
      }
    }
  }
}

void compareAllowNotFound(const std::shared_ptr<ServerEntry>& master,
                          const std::shared_ptr<ServerEntry>& slave) {
  INVARIANT(master->getKVStoreCount() == slave->getKVStoreCount());

  for (size_t i = 0; i < master->getKVStoreCount(); i++) {
    uint64_t count1 = 0;
    uint64_t count2 = 0;
    uint64_t notFoundNum = 0;
    auto kvstore1 = master->getStores()[i];
    auto kvstore2 = slave->getStores()[i];

    auto ptxn2 = kvstore2->createTransaction(nullptr);
    EXPECT_TRUE(ptxn2.ok());
    std::unique_ptr<Transaction> txn2 = std::move(ptxn2.value());

    auto ptxn1 = kvstore1->createTransaction(nullptr);
    EXPECT_TRUE(ptxn1.ok());
    std::unique_ptr<Transaction> txn1 = std::move(ptxn1.value());
    auto cursor1 = txn1->createAllDataCursor();
    auto cursor1_binlog = txn1->createBinlogCursor();
    while (true) {
      Expected<Record> exptRcd1 = cursor1->next();
      if (exptRcd1.status().code() == ErrorCodes::ERR_EXHAUST) {
        break;
      }
      INVARIANT(exptRcd1.ok());
      count1++;

      auto exptRcdv2 =
        kvstore2->getKV(exptRcd1.value().getRecordKey(), txn2.get());
      if (exptRcdv2.ok()) {
        EXPECT_EQ(exptRcd1.value().getRecordValue(), exptRcdv2.value());
      } else {
        notFoundNum++;
      }
    }
    // check the binlog
    while (true) {
      Expected<Record> exptRcd1 = cursor1_binlog->next();
      if (exptRcd1.status().code() == ErrorCodes::ERR_EXHAUST) {
        break;
      }
      INVARIANT(exptRcd1.ok());
      count1++;

      auto exptRcdv2 =
        kvstore2->getKV(exptRcd1.value().getRecordKey(), txn2.get());
      if (exptRcdv2.ok()) {
        EXPECT_EQ(exptRcd1.value().getRecordValue(), exptRcdv2.value());
      } else {
        notFoundNum++;
      }
    }

    auto cursor2 = txn2->createAllDataCursor();
    auto cursor2_binlog = txn2->createBinlogCursor();
    while (true) {
      Expected<Record> exptRcd2 = cursor2->next();
      if (exptRcd2.status().code() == ErrorCodes::ERR_EXHAUST) {
        break;
      }
      INVARIANT(exptRcd2.ok());
      count2++;
    }
    while (true) {
      Expected<Record> exptRcd2 = cursor2_binlog->next();
      if (exptRcd2.status().code() == ErrorCodes::ERR_EXHAUST) {
        break;
      }
      INVARIANT(exptRcd2.ok());
      count2++;
    }

    if (count1 == 0) {
      EXPECT_EQ(count1, count2);
    } else {
      if (count1 != count2) {
        if (count1 == 2) {
          // master2 will dont has datakey and binlogkey.
          EXPECT_EQ(count2, 0);
        } else {
          // master2 datakey num will be one less, binlogkey num be
          // the same.
          EXPECT_EQ(count1, count2 + 1);
        }
        // the last datakey and the binlogkey will cant found.
        EXPECT_EQ(notFoundNum, 2);
      }
    }
    LOG(INFO) << "compare data: store " << i << " count1:" << count1
              << " count2:" << count2 << " notFoundNum:" << notFoundNum;
  }
}

std::vector<uint32_t> getKeyNum(const std::shared_ptr<ServerEntry>& master) {
  std::vector<uint32_t> keyNum;
  for (size_t i = 0; i < master->getKVStoreCount(); i++) {
    uint64_t count = 0;
    auto kvstore = master->getStores()[i];

    auto ptxn = kvstore->createTransaction(nullptr);
    EXPECT_TRUE(ptxn.ok());
    std::unique_ptr<Transaction> txn = std::move(ptxn.value());
    auto cursor = txn->createAllDataCursor();
    auto cursor_binlog = txn->createBinlogCursor();
    while (true) {
      Expected<Record> exptRcd = cursor->next();
      if (exptRcd.status().code() == ErrorCodes::ERR_EXHAUST) {
        break;
      }
      INVARIANT(exptRcd.ok());
      count++;
    }
    while (true) {
      Expected<Record> exptRcd = cursor_binlog->next();
      if (exptRcd.status().code() == ErrorCodes::ERR_EXHAUST) {
        break;
      }
      INVARIANT(exptRcd.ok());
      count++;
    }
    keyNum.push_back(count);
  }
  return keyNum;
}

void checkNumAllowDiff(std::vector<uint32_t> nums1,
                       std::vector<uint32_t> nums2) {
  EXPECT_EQ(nums1.size(), nums2.size());
  for (size_t i = 0; i < nums1.size(); ++i) {
    // if 2nd time not add, num will be equal
    if (nums1[i] != nums2[i]) {
      // master has one datakey less.
      // if only store one key, master2 has no datakey and binlogkey, so
      // be 2 num less.
      LOG(INFO) << "checkNumAllowDiff, i:" << i
        << "nums1:" << nums1[i] << " nums2:" << nums2[i];
      EXPECT_TRUE(nums1[i] == nums2[i] + 1 || nums1[i] == nums2[i] + 2);
    }
  }
}

std::pair<std::shared_ptr<ServerEntry>, std::shared_ptr<ServerEntry>>
makeRestoreEnv(uint32_t storeCnt) {
  EXPECT_TRUE(setupEnv(master1_dir));
  EXPECT_TRUE(setupEnv(master2_dir));

  auto cfg1 = makeServerParam(master1_port, storeCnt, master1_dir, false);
  auto cfg2 = makeServerParam(master2_port, storeCnt, master2_dir, false);
  cfg1->maxBinlogKeepNum = 1;
  cfg2->maxBinlogKeepNum = 1;
  cfg1->minBinlogKeepSec = 0;
  cfg2->minBinlogKeepSec = 0;
  cfg1->binlogDelRange = 1;
  cfg2->binlogDelRange = 1;

  auto master1 = std::make_shared<ServerEntry>(cfg1);
  auto s = master1->startup(cfg1);
  INVARIANT(s.ok());

  auto master2 = std::make_shared<ServerEntry>(cfg2);
  s = master2->startup(cfg2);
  return std::make_pair(master1, master2);
}

#ifdef _WIN32
size_t recordSize = 10;
#else
size_t recordSize = 100;
#endif

TEST(Restore, Common) {
#ifdef _WIN32
  size_t i = 1;
  {
#else
  for (size_t i = 0; i < 2; i++) {
#endif
    LOG(INFO) << ">>>>>> test store count:" << i;

    const auto guard = MakeGuard([] {
      destroyEnv(master1_dir);
      destroyEnv(master2_dir);
      std::this_thread::sleep_for(std::chrono::seconds(1));
    });

    auto hosts = makeRestoreEnv(i);
    auto& master1 = hosts.first;
    auto& master2 = hosts.second;

    // make sure no binlog been deleted
    runCommand(master1, {"config", "set", "maxBinlogKeepNum", "1000000000"});
    runCommand(master2, {"config", "set", "maxBinlogKeepNum", "1000000000"});

    auto allKeys1 = initData(master1, recordSize, "suffix1");
    LOG(INFO) << ">>>>>> master1 initData 1st end;";
    backup(master1, "copy");
    restoreBackup(master2);
    LOG(INFO) << ">>>>>> master2 restoreBackup end;";
    compareData(master1, master2);  // compare data + binlog
    LOG(INFO) << ">>>>>> compareData 1st end;";

    backup(master1, "ckpt");
    restoreBackup(master2);
    LOG(INFO) << ">>>>>> master2 restoreBackup end;";
    compareData(master1, master2);  // compare data + binlog
    LOG(INFO) << ">>>>>> compareData 1st end;";

    // make sure binlog should been deleted
    runCommand(master1, {"config", "set", "maxBinlogKeepNum", "1"});
    runCommand(master2, {"config", "set", "maxBinlogKeepNum", "1"});

    uint32_t part1_num = std::rand() % recordSize;
    part1_num = part1_num == 0 ? 1 : part1_num;
    uint32_t part2_num = recordSize - part1_num;
    // add kv only
    auto partKeys2 = initKvData(master1, part1_num, "suffix21");
    waitBinlogDump(master1);
    std::vector<uint32_t> m1_keynum1 = getKeyNum(master1);
    LOG(INFO) << ">>>>>> master1 initKvData 1st end;";
    uint64_t ts = msSinceEpoch();
    LOG(INFO) << "ms:" << ts;
    sleep(1);  // wait ts changed
    // add kv only
    auto partKeys3 = initKvData(master1, part2_num, "suffix22");
    waitBinlogDump(master1);
    std::vector<uint32_t> m1_keynum2 = getKeyNum(master1);
    LOG(INFO) << ">>>>>> master1 initKvData 2st end;";
    flushBinlog(master1);
    restoreBinlog(master1_dir, master2, ts);
    LOG(INFO) << ">>>>>> master2 restoreBinlog 1st end;";
    waitBinlogDump(master2);
    std::vector<uint32_t> m2_keynum1 = getKeyNum(master2);
    // if a kvstore write some key in the second time,
    // the keynum will equal, otherwise the keynum will be one less
    checkNumAllowDiff(m1_keynum1, m2_keynum1);  // check num only
    flushBinlog(master1);
    restoreBinlog(master1_dir, master2, UINT64_MAX);
    LOG(INFO) << ">>>>>> master2 restoreBinlog 2st end;";
    waitBinlogDump(master2);
    std::vector<uint32_t> m2_keynum2 = getKeyNum(master2);
    checkNumAllowDiff(m1_keynum2, m2_keynum2);  // check num only
    compareAllowNotFound(master1, master2);
    LOG(INFO) << ">>>>>> compareData 2st end;";

    testAll(master1);
    addOneKeyEveryKvstore(master1, "restore_test_key1");
    waitBinlogDump(master1);
    flushBinlog(master1);
    restoreBinlog(master1_dir, master2, UINT64_MAX);
    addOneKeyEveryKvstore(master2, "restore_test_key1");
    waitBinlogDump(master2);
    compareData(master1, master2, false);  // compare data only

    master1->stop();
    master2->stop();
    ASSERT_EQ(master1.use_count(), 1);
    ASSERT_EQ(master2.use_count(), 1);
    LOG(INFO) << ">>>>>> test store count:" << i << " end;";
  }
}

std::vector<std::shared_ptr<ServerEntry>> makeRestoreEnv2(uint32_t storeCnt) {
  EXPECT_TRUE(setupEnv(master1_dir));
  EXPECT_TRUE(setupEnv(master2_dir));
  EXPECT_TRUE(setupEnv(slave1_dir));

  auto cfg1 = makeServerParam(master1_port, storeCnt, master1_dir, false);
  auto cfg2 = makeServerParam(master2_port, storeCnt, master2_dir, false);
  auto cfg3 = makeServerParam(slave1_port, storeCnt, slave1_dir, false);
  cfg1->minBinlogKeepSec = 60;
  cfg1->binlogDelRange = 1;
  cfg2->maxBinlogKeepNum = 1;
  cfg2->minBinlogKeepSec = 0;
  cfg2->binlogDelRange = 1;
  cfg3->maxBinlogKeepNum = 1;
  cfg3->minBinlogKeepSec = 0;
  cfg3->binlogDelRange = 1;
  cfg3->slaveBinlogKeepNum = 1;

  auto master1 = std::make_shared<ServerEntry>(cfg1);
  auto s = master1->startup(cfg1);
  INVARIANT(s.ok());

  auto master2 = std::make_shared<ServerEntry>(cfg2);
  s = master2->startup(cfg2);

  auto slave1 = std::make_shared<ServerEntry>(cfg3);
  s = slave1->startup(cfg3);
  return std::vector<std::shared_ptr<ServerEntry>>({master1, master2, slave1});
}

TEST(Restore, Common2) {
#ifdef _WIN32
  size_t i = 1;
  {
#else
  for (size_t i = 0; i < 2; i++) {
#endif
    LOG(INFO) << ">>>>>> test store count:" << i;

    const auto guard = MakeGuard([] {
      destroyEnv(master1_dir);
      destroyEnv(master2_dir);
      destroyEnv(slave1_dir);
      std::this_thread::sleep_for(std::chrono::seconds(1));
    });

    auto hosts = makeRestoreEnv2(i);
    auto& master1 = hosts[0];
    auto& master2 = hosts[1];
    auto& slave1 = hosts[2];
    {
      auto ctx = std::make_shared<asio::io_context>();
      auto session = makeSession(slave1, ctx);
      WorkLoad work(slave1, session);
      work.init();
      work.slaveof("127.0.0.1", master1_port);
    }

    LOG(INFO) << ">>>>>> master1 add data begin.";
    auto thread = std::thread([this, master1]() {
      testAll(master1);  // need about 40 seconds
    });
    uint32_t sleep_time = genRand() % 20 + 10;  // 10-30 seconds
    sleep(sleep_time);
    LOG(INFO) << ">>>>>> master1 backup and master2 restoreBackup.";
    backup(slave1, "ckpt");
    restoreBackup(master2);
    thread.join();
    LOG(INFO) << ">>>>>> master1 add data end.";
    addOneKeyEveryKvstore(master1, "restore_test_key1");
    addOneKeyEveryKvstore(master1, "restore_test_key2");
    waitSlaveCatchup(master1, slave1);
    waitBinlogDump(slave1);
    flushBinlog(slave1);
    restoreBinlog(slave1_dir, master2, UINT64_MAX);
    addOneKeyEveryKvstore(master2, "restore_test_key1");
    addOneKeyEveryKvstore(master2, "restore_test_key2");
    waitBinlogDump(master2);
    compareData(master1, master2, false);  // compare data only

    master1->stop();
    master2->stop();
    slave1->stop();
    ASSERT_EQ(master1.use_count(), 1);
    ASSERT_EQ(master2.use_count(), 1);
    ASSERT_EQ(slave1.use_count(), 1);
    LOG(INFO) << ">>>>>> test store count:" << i << " end;";
  }
}
}  // namespace tendisplus


// Please refer to the license text that comes with this tendis open source
// project for additional information.

#include <stdlib.h>

#include <memory>
#include <utility>
#include <thread>  // NOLINT
#include <string>
#include <vector>
#include <algorithm>

#include "gtest/gtest.h"
#include "glog/logging.h"

#include "tendisplus/server/server_entry.h"
#include "tendisplus/server/index_manager.h"
#include "tendisplus/server/segment_manager.h"
#include "tendisplus/commands/command.h"
#include "tendisplus/network/network.h"
#include "tendisplus/utils/test_util.h"
#include "tendisplus/utils/scopeguard.h"
#include "tendisplus/utils/sync_point.h"
#include "tendisplus/utils/invariant.h"

namespace tendisplus {

const char* master1_dir = "restoretest_master1";
const char* master2_dir = "restoretest_master2";
const char* slave1_dir = "restoretest_slave1";
uint32_t master1_port = 12001;
uint32_t master2_port = 12002;
uint32_t slave1_port = 12003;

AllKeys initData(std::shared_ptr<ServerEntry> server,
                 uint32_t count,
                 const char* key_suffix) {
  auto ctx1 = std::make_shared<asio::io_context>();
  auto sess1 = makeSession(server, ctx1);
  WorkLoad work(server, sess1);
  work.init();

  AllKeys all_keys;

  auto kv_keys = work.writeWork(RecordType::RT_KV, count, 0, true, key_suffix);
  all_keys.emplace_back(kv_keys);

  auto list_keys =
    work.writeWork(RecordType::RT_LIST_META, count, 2, true, key_suffix);
  all_keys.emplace_back(list_keys);

  auto hash_keys =
    work.writeWork(RecordType::RT_HASH_META, count, 2, true, key_suffix);
  all_keys.emplace_back(hash_keys);

  auto set_keys =
    work.writeWork(RecordType::RT_SET_META, count, 2, true, key_suffix);
  all_keys.emplace_back(set_keys);

  auto zset_keys =
    work.writeWork(RecordType::RT_ZSET_META, count, 2, true, key_suffix);
  all_keys.emplace_back(zset_keys);

  return all_keys;
}

AllKeys initKvData(const std::shared_ptr<ServerEntry>& server,
                   uint32_t count,
                   const char* key_suffix) {
  auto ctx1 = std::make_shared<asio::io_context>();
  auto sess1 = makeSession(server, ctx1);
  WorkLoad work(server, sess1);
  work.init();

  AllKeys all_keys;

  auto kv_keys = work.writeWork(RecordType::RT_KV, count, 0, true, key_suffix);
  all_keys.emplace_back(kv_keys);

  return std::move(all_keys);
}

void addOneKeyEveryKvstore(const std::shared_ptr<ServerEntry>& server,
                           const char* key) {
  for (size_t i = 0; i < server->getKVStoreCount(); i++) {
    auto kvstore = server->getStores()[i];
    auto eTxn1 = kvstore->createTransaction(nullptr);
    EXPECT_EQ(eTxn1.ok(), true);
    std::unique_ptr<Transaction> txn1 = std::move(eTxn1.value());

    Status s =
      kvstore->setKV(Record(RecordKey(0, 0, RecordType::RT_KV, key, ""),
                            RecordValue("txn1", RecordType::RT_KV, -1)),
                     txn1.get());
    EXPECT_EQ(s.ok(), true);

    Expected<uint64_t> exptCommitId = txn1->commit();
    EXPECT_TRUE(exptCommitId.ok());
  }
}

void backup(const std::shared_ptr<ServerEntry>& server, const string& mode) {
  auto ctx = std::make_shared<asio::io_context>();
  auto sess = makeSession(server, ctx);

  // clear data
  const char* dir = "./back_test";
  if (filesystem::exists(dir)) {
    filesystem::remove_all(dir);
  }
  filesystem::create_directory(dir);

  std::vector<std::string> args;
  args.push_back("backup");
  args.push_back(dir);
  args.push_back(mode);
  sess->setArgs(args);
  auto expect = Command::runSessionCmd(sess.get());
  EXPECT_TRUE(expect.ok());
}

void restoreBackup(const std::shared_ptr<ServerEntry>& server) {
  auto ctx = std::make_shared<asio::io_context>();
  auto sess = makeSession(server, ctx);

  std::vector<std::string> args;
  args.push_back("restorebackup");
  args.push_back("all");
  args.push_back("./back_test");
  args.push_back("force");
  sess->set

  auto expect = Command::runSessionCmd(sess.get());
  EXPECT_TRUE(expect.ok());
}

void flushBinlog(const std::shared_ptr<ServerEntry>& server) {
  auto ctx = std::make_shared<asio::io_context>();
  auto sess = makeSession(server, ctx);

  for (size_t i = 0; i < server->getKVStoreCount(); i++) {
    std::vector<std::string> args;
    args.push_back("binlogflush");
    args.push_back(std::to_string(i));
    sess->setArgs(args);
    auto expect = Command::runSessionCmd(sess.get());
    EXPECT_TRUE(expect.ok());
  }
}

#ifdef _WIN32
#define popen _popen
#define pclose _pclose
#endif

bool runShell(const std::string& cmd) {
  FILE* fstream = NULL;
  char buff[1024];
  bool success = true;
  memset(buff, 0, sizeof(buff));

  if (NULL == (fstream = popen(cmd.c_str(), "r"))) {
    INVARIANT(0);
    return false;
  }

  memset(buff, 0x00, sizeof(buff));
  while (NULL != fgets(buff, sizeof(buff), fstream)) {
    if (buff[0] != '\0') {
      LOG(INFO) << std::string(buff);
      memset(buff, 0x00, sizeof(buff));
      success = false;
    }
  }
  pclose(fstream);
  return success;
}

void restoreBinlog(const string& src_binlog_dir,
                   const std::shared_ptr<ServerEntry>& server,
                   uint64_t end_ts = UINT64_MAX) {
  for (size_t i = 0; i < server->getKVStoreCount(); i++) {
    auto kvstore = server->getStores()[i];
    uint64_t binglogPos = kvstore->getHighestBinlogId();

    std::string subpath =
      "./" + src_binlog_dir + "/dump/" + std::to_string(i) + "/";
    std::vector<std::string> loglist;
    for (auto& p : filesystem::recursive_directory_iterator(subpath)) {
      const filesystem::path& path = p.path();
      if (!filesystem::is_regular_file(p)) {
        LOG(INFO) << "maxDumpFileSeq ignore:" << p.path();
        continue;
      }
      // assert path with dir prefix
#ifndef _WIN32
      INVARIANT(path.string().find(subpath) == 0);
#endif
      std::string relative = path.string().erase(0, subpath.size());
      if (relative.substr(0, 6) != "binlog") {
        LOG(INFO) << "maxDumpFileSeq ignore:" << relative;
        continue;
      }
      LOG(INFO) << "binlog file:" << p.path();
      loglist.push_back(p.path().u8string());
    }
    if (loglist.size() <= 0) {
      continue;
    }
    std::sort(loglist.begin(), loglist.end());
    for (size_t j = 0; j < loglist.size(); ++j) {
      std::string cmd = "./build/bin/binlog_tool";
      cmd += " --logfile=" + loglist[j];
      cmd += " --mode=base64";
      cmd += " --start-position=" + std::to_string(binglogPos + 1);
      cmd += " --end-datetime=" + std::to_string(end_ts);
      cmd += "| ./bin/redis-cli --csv -p " + std::to_string(master2_port);
      cmd += "| grep -v OK";
      LOG(INFO) << cmd;

      EXPECT_TRUE(runShell(cmd));
    }
  }
}

void waitBinlogDump(const std::shared_ptr<ServerEntry>& server) {
  INVARIANT(server->getParams()->maxBinlogKeepNum == 1);
  for (size_t i = 0; i < server->getKVStoreCount(); i++) {
    auto kvstore = server->getStores()[i];

    while (true) {
      auto ptxn = kvstore->createTransaction(nullptr);
      EXPECT_TRUE(ptxn.ok());
      std::unique_ptr<Transaction> txn = std::move(ptxn.value());
      uint64_t minBinlogId = 0;
      auto expBinlogidMin = RepllogCursorV2::getMinBinlogId(txn.get());
      if (expBinlogidMin.status().code() == ErrorCodes::ERR_EXHAUST) {
        minBinlogId = 0;
      } else {
        EXPECT_TRUE(expBinlogidMin.ok());
        minBinlogId = expBinlogidMin.value();
      }

      uint64_t maxBinlogId = 0;
      auto expBinlogidMax = RepllogCursorV2::getMaxBinlogId(txn.get());
      if (expBinlogidMax.status().code() == ErrorCodes::ERR_EXHAUST) {
        maxBinlogId = 0;
      } else {
        EXPECT_TRUE(expBinlogidMin.ok());
        maxBinlogId = expBinlogidMax.value();
      }

      // wait only one binlog left in rocksdb
      if (minBinlogId == maxBinlogId) {
        break;
      } else {
        std::this_thread::sleep_for(10ms);
      }
    }
  }
}

void compareAllowNotFound(const std::shared_ptr<ServerEntry>& master,
                          const std::shared_ptr<ServerEntry>& slave) {
  INVARIANT(master->getKVStoreCount() == slave->getKVStoreCount());

  for (size_t i = 0; i < master->getKVStoreCount(); i++) {
    uint64_t count1 = 0;
    uint64_t count2 = 0;
    uint64_t notFoundNum = 0;
    auto kvstore1 = master->getStores()[i];
    auto kvstore2 = slave->getStores()[i];

    auto ptxn2 = kvstore2->createTransaction(nullptr);
    EXPECT_TRUE(ptxn2.ok());
    std::unique_ptr<Transaction> txn2 = std::move(ptxn2.value());

    auto ptxn1 = kvstore1->createTransaction(nullptr);
    EXPECT_TRUE(ptxn1.ok());
    std::unique_ptr<Transaction> txn1 = std::move(ptxn1.value());
    auto cursor1 = txn1->createAllDataCursor();
    auto cursor1_binlog = txn1->createBinlogCursor();
    while (true) {
      Expected<Record> exptRcd1 = cursor1->next();
      if (exptRcd1.status().code() == ErrorCodes::ERR_EXHAUST) {
        break;
      }
      INVARIANT(exptRcd1.ok());
      count1++;

      auto exptRcdv2 =
        kvstore2->getKV(exptRcd1.value().getRecordKey(), txn2.get());
      if (exptRcdv2.ok()) {
        EXPECT_EQ(exptRcd1.value().getRecordValue(), exptRcdv2.value());
      } else {
        notFoundNum++;
      }
    }
    // check the binlog
    while (true) {
      Expected<Record> exptRcd1 = cursor1_binlog->next();
      if (exptRcd1.status().code() == ErrorCodes::ERR_EXHAUST) {
        break;
      }
      INVARIANT(exptRcd1.ok());
      count1++;

      auto exptRcdv2 =
        kvstore2->getKV(exptRcd1.value().getRecordKey(), txn2.get());
      if (exptRcdv2.ok()) {
        EXPECT_EQ(exptRcd1.value().getRecordValue(), exptRcdv2.value());
      } else {
        notFoundNum++;
      }
    }

    auto cursor2 = txn2->createAllDataCursor();
    auto cursor2_binlog = txn2->createBinlogCursor();
    while (true) {
      Expected<Record> exptRcd2 = cursor2->next();
      if (exptRcd2.status().code() == ErrorCodes::ERR_EXHAUST) {
        break;
      }
      INVARIANT(exptRcd2.ok());
      count2++;
    }
    while (true) {
      Expected<Record> exptRcd2 = cursor2_binlog->next();
      if (exptRcd2.status().code() == ErrorCodes::ERR_EXHAUST) {
        break;
      }
      INVARIANT(exptRcd2.ok());
      count2++;
    }

    if (count1 == 0) {
      EXPECT_EQ(count1, count2);
    } else {
      if (count1 != count2) {
        if (count1 == 2) {
          // master2 will dont has datakey and binlogkey.
          EXPECT_EQ(count2, 0);
        } else {
          // master2 datakey num will be one less, binlogkey num be
          // the same.
          EXPECT_EQ(count1, count2 + 1);
        }
        // the last datakey and the binlogkey will cant found.
        EXPECT_EQ(notFoundNum, 2);
      }
    }
    LOG(INFO) << "compare data: store " << i << " count1:" << count1
              << " count2:" << count2 << " notFoundNum:" << notFoundNum;
  }
}

std::vector<uint32_t> getKeyNum(const std::shared_ptr<ServerEntry>& master) {
  std::vector<uint32_t> keyNum;
  for (size_t i = 0; i < master->getKVStoreCount(); i++) {
    uint64_t count = 0;
    auto kvstore = master->getStores()[i];

    auto ptxn = kvstore->createTransaction(nullptr);
    EXPECT_TRUE(ptxn.ok());
    std::unique_ptr<Transaction> txn = std::move(ptxn.value());
    auto cursor = txn->createAllDataCursor();
    auto cursor_binlog = txn->createBinlogCursor();
    while (true) {
      Expected<Record> exptRcd = cursor->next();
      if (exptRcd.status().code() == ErrorCodes::ERR_EXHAUST) {
        break;
      }
      INVARIANT(exptRcd.ok());
      count++;
    }
    while (true) {
      Expected<Record> exptRcd = cursor_binlog->next();
      if (exptRcd.status().code() == ErrorCodes::ERR_EXHAUST) {
        break;
      }
      INVARIANT(exptRcd.ok());
      count++;
    }
    keyNum.push_back(count);
  }
  return keyNum;
}

void checkNumAllowDiff(std::vector<uint32_t> nums1,
                       std::vector<uint32_t> nums2) {
  EXPECT_EQ(nums1.size(), nums2.size());
  for (size_t i = 0; i < nums1.size(); ++i) {
    // if 2nd time not add, num will be equal
    if (nums1[i] != nums2[i]) {
      // master has one datakey less.
      // if only store one key, master2 has no datakey and binlogkey, so
      // be 2 num less.
      LOG(INFO) << "checkNumAllowDiff, i:" << i
        << "nums1:" << nums1[i] << " nums2:" << nums2[i];
      EXPECT_TRUE(nums1[i] == nums2[i] + 1 || nums1[i] == nums2[i] + 2);
    }
  }
}

std::pair<std::shared_ptr<ServerEntry>, std::shared_ptr<ServerEntry>>
makeRestoreEnv(uint32_t storeCnt) {
  EXPECT_TRUE(setupEnv(master1_dir));
  EXPECT_TRUE(setupEnv(master2_dir));

  auto cfg1 = makeServerParam(master1_port, storeCnt, master1_dir, false);
  auto cfg2 = makeServerParam(master2_port, storeCnt, master2_dir, false);
  cfg1->maxBinlogKeepNum = 1;
  cfg2->maxBinlogKeepNum = 1;
  cfg1->minBinlogKeepSec = 0;
  cfg2->minBinlogKeepSec = 0;
  cfg1->binlogDelRange = 1;
  cfg2->binlogDelRange = 1;

  auto master1 = std::make_shared<ServerEntry>(cfg1);
  auto s = master1->startup(cfg1);
  INVARIANT(s.ok());

  auto master2 = std::make_shared<ServerEntry>(cfg2);
  s = master2->startup(cfg2);
  return std::make_pair(master1, master2);
}

#ifdef _WIN32
size_t recordSize = 10;
#else
size_t recordSize = 100;
#endif

TEST(Restore, Common) {
#ifdef _WIN32
  size_t i = 1;
  {
#else
  for (size_t i = 0; i < 2; i++) {
#endif
    LOG(INFO) << ">>>>>> test store count:" << i;

    const auto guard = MakeGuard([] {
      destroyEnv(master1_dir);
      destroyEnv(master2_dir);
      std::this_thread::sleep_for(std::chrono::seconds(1));
    });

    auto hosts = makeRestoreEnv(i);
    auto& master1 = hosts.first;
    auto& master2 = hosts.second;

    // make sure no binlog been deleted
    runCommand(master1, {"config", "set", "maxBinlogKeepNum", "1000000000"});
    runCommand(master2, {"config", "set", "maxBinlogKeepNum", "1000000000"});

    auto allKeys1 = initData(master1, recordSize, "suffix1");
    LOG(INFO) << ">>>>>> master1 initData 1st end;";
    backup(master1, "copy");
    restoreBackup(master2);
    LOG(INFO) << ">>>>>> master2 restoreBackup end;";
    compareData(master1, master2);  // compare data + binlog
    LOG(INFO) << ">>>>>> compareData 1st end;";

    backup(master1, "ckpt");
    restoreBackup(master2);
    LOG(INFO) << ">>>>>> master2 restoreBackup end;";
    compareData(master1, master2);  // compare data + binlog
    LOG(INFO) << ">>>>>> compareData 1st end;";

    // make sure binlog should been deleted
    runCommand(master1, {"config", "set", "maxBinlogKeepNum", "1"});
    runCommand(master2, {"config", "set", "maxBinlogKeepNum", "1"});

    uint32_t part1_num = std::rand() % recordSize;
    part1_num = part1_num == 0 ? 1 : part1_num;
    uint32_t part2_num = recordSize - part1_num;
    // add kv only
    auto partKeys2 = initKvData(master1, part1_num, "suffix21");
    waitBinlogDump(master1);
    std::vector<uint32_t> m1_keynum1 = getKeyNum(master1);
    LOG(INFO) << ">>>>>> master1 initKvData 1st end;";
    uint64_t ts = msSinceEpoch();
    LOG(INFO) << "ms:" << ts;
    sleep(1);  // wait ts changed
    // add kv only
    auto partKeys3 = initKvData(master1, part2_num, "suffix22");
    waitBinlogDump(master1);
    std::vector<uint32_t> m1_keynum2 = getKeyNum(master1);
    LOG(INFO) << ">>>>>> master1 initKvData 2st end;";
    flushBinlog(master1);
    restoreBinlog(master1_dir, master2, ts);
    LOG(INFO) << ">>>>>> master2 restoreBinlog 1st end;";
    waitBinlogDump(master2);
    std::vector<uint32_t> m2_keynum1 = getKeyNum(master2);
    // if a kvstore write some key in the second time,
    // the keynum will equal, otherwise the keynum will be one less
    checkNumAllowDiff(m1_keynum1, m2_keynum1);  // check num only
    flushBinlog(master1);
    restoreBinlog(master1_dir, master2, UINT64_MAX);
    LOG(INFO) << ">>>>>> master2 restoreBinlog 2st end;";
    waitBinlogDump(master2);
    std::vector<uint32_t> m2_keynum2 = getKeyNum(master2);
    checkNumAllowDiff(m1_keynum2, m2_keynum2);  // check num only
    compareAllowNotFound(master1, master2);
    LOG(INFO) << ">>>>>> compareData 2st end;";

    testAll(master1);
    addOneKeyEveryKvstore(master1, "restore_test_key1");
    waitBinlogDump(master1);
    flushBinlog(master1);
    restoreBinlog(master1_dir, master2, UINT64_MAX);
    addOneKeyEveryKvstore(master2, "restore_test_key1");
    waitBinlogDump(master2);
    compareData(master1, master2, false);  // compare data only

    master1->stop();
    master2->stop();
    ASSERT_EQ(master1.use_count(), 1);
    ASSERT_EQ(master2.use_count(), 1);
    LOG(INFO) << ">>>>>> test store count:" << i << " end;";
  }
}

std::vector<std::shared_ptr<ServerEntry>> makeRestoreEnv2(uint32_t storeCnt) {
  EXPECT_TRUE(setupEnv(master1_dir));
  EXPECT_TRUE(setupEnv(master2_dir));
  EXPECT_TRUE(setupEnv(slave1_dir));

  auto cfg1 = makeServerParam(master1_port, storeCnt, master1_dir, false);
  auto cfg2 = makeServerParam(master2_port, storeCnt, master2_dir, false);
  auto cfg3 = makeServerParam(slave1_port, storeCnt, slave1_dir, false);
  cfg1->minBinlogKeepSec = 60;
  cfg1->binlogDelRange = 1;
  cfg2->maxBinlogKeepNum = 1;
  cfg2->minBinlogKeepSec = 0;
  cfg2->binlogDelRange = 1;
  cfg3->maxBinlogKeepNum = 1;
  cfg3->minBinlogKeepSec = 0;
  cfg3->binlogDelRange = 1;
  cfg3->slaveBinlogKeepNum = 1;

  auto master1 = std::make_shared<ServerEntry>(cfg1);
  auto s = master1->startup(cfg1);
  INVARIANT(s.ok());

  auto master2 = std::make_shared<ServerEntry>(cfg2);
  s = master2->startup(cfg2);

  auto slave1 = std::make_shared<ServerEntry>(cfg3);
  s = slave1->startup(cfg3);
  return std::vector<std::shared_ptr<ServerEntry>>({master1, master2, slave1});
}

TEST(Restore, Common2) {
#ifdef _WIN32
  size_t i = 1;
  {
#else
  for (size_t i = 0; i < 2; i++) {
#endif
    LOG(INFO) << ">>>>>> test store count:" << i;

    const auto guard = MakeGuard([] {
      destroyEnv(master1_dir);
      destroyEnv(master2_dir);
      destroyEnv(slave1_dir);
      std::this_thread::sleep_for(std::chrono::seconds(1));
    });

    auto hosts = makeRestoreEnv2(i);
    auto& master1 = hosts[0];
    auto& master2 = hosts[1];
    auto& slave1 = hosts[2];
    {
      auto ctx = std::make_shared<asio::io_context>();
      auto session = makeSession(slave1, ctx);
      WorkLoad work(slave1, session);
      work.init();
      work.slaveof("127.0.0.1", master1_port);
    }

    LOG(INFO) << ">>>>>> master1 add data begin.";
    auto thread = std::thread([this, master1]() {
      testAll(master1);  // need about 40 seconds
    });
    uint32_t sleep_time = genRand() % 20 + 10;  // 10-30 seconds
    sleep(sleep_time);
    LOG(INFO) << ">>>>>> master1 backup and master2 restoreBackup.";
    backup(slave1, "ckpt");
    restoreBackup(master2);
    thread.join();
    LOG(INFO) << ">>>>>> master1 add data end.";
    addOneKeyEveryKvstore(master1, "restore_test_key1");
    addOneKeyEveryKvstore(master1, "restore_test_key2");
    waitSlaveCatchup(master1, slave1);
    waitBinlogDump(slave1);
    flushBinlog(slave1);
    restoreBinlog(slave1_dir, master2, UINT64_MAX);
    addOneKeyEveryKvstore(master2, "restore_test_key1");
    addOneKeyEveryKvstore(master2, "restore_test_key2");
    waitBinlogDump(master2);
    compareData(master1, master2, false);  // compare data only

    master1->stop();
    master2->stop();
    slave1->stop();
    ASSERT_EQ(master1.use_count(), 1);
    ASSERT_EQ(master2.use_count(), 1);
    ASSERT_EQ(slave1.use_count(), 1);
    LOG(INFO) << ">>>>>> test store count:" << i << " end;";
  }
}
}  // namespace tendisplus