#ifndef TH_GENERIC_FILE
#define TH_GENERIC_FILE "generic/TemporalSubSampling.c"
#else

static inline void THNN_(TemporalSubSampling_shapeCheck)(
                         THNNState *state,
                         THTensor *input,
                         THTensor *gradOutput,
                         int kW,
                         int dW,
                         int *inputFrameSize) {
  int nInputFrame, nOutputFrame;

  THArgCheck(kW > 0, 6,
             "kernel size should be greater than zero, but got kW: %d", kW);
  THArgCheck(dW > 0, 7,
             "stride should be greater than zero, but got dW: %d", dW);

  THNN_ARGCHECK(input->nDimension == 2, 2, input,
                  "2D or 3D (batch mode) tensor expected for input, but got: %s");
  if (inputFrameSize != NULL) {
    THArgCheck( input->size[1] == *inputFrameSize, 2,
                "invalid input frame size.  Got: %d, Expected: %d",
                input->size[1], *inputFrameSize);
  }
  THArgCheck( input->size[0] >= kW, 2,
              "input sequence smaller than kernel size.  Got %d, Expected: %d",
              input->size[0], kW);

  nInputFrame = input->size[0];
  nOutputFrame = (nInputFrame - kW) / dW + 1;

  if (gradOutput != NULL) {
    THNN_CHECK_DIM_SIZE(gradOutput, input->nDimension, 0, nOutputFrame);
    if (inputFrameSize != NULL) {
      THNN_CHECK_DIM_SIZE(gradOutput, input->nDimension, 1, *inputFrameSize);
    }
  }
}

void THNN_(TemporalSubSampling_updateOutput)(
          THNNState *state,
          THTensor *input,
          THTensor *output,
          THTensor *weight,
          THTensor *bias,
          int kW,
          int dW,
          int inputFrameSize)
{
  THTensor *outputFrame, *inputWindow;
  int nInputFrame, nOutputFrame;
  long k;

  THArgCheck(THTensor_(isContiguous)(weight), 4, "weight must be contiguous");
  THArgCheck(!bias || THTensor_(isContiguous)(bias), 4, "bias must be contiguous");
  THNN_(TemporalSubSampling_shapeCheck)(state, input, NULL, kW, dW, &inputFrameSize);

  outputFrame = THTensor_(new)();
  inputWindow = THTensor_(new)();

  nInputFrame = input->size[0];
  nOutputFrame = (nInputFrame - kW) / dW + 1;

  THTensor_(resize2d)(output,
                      nOutputFrame,
                      inputFrameSize);

  for(k = 0; k < nOutputFrame; k++)
  {
    THTensor_(narrow)(inputWindow, input, 0, k*dW, kW);
    THTensor_(select)(outputFrame, output, 0, k);
    THTensor_(sum)(outputFrame, inputWindow, 0, 1);
    THTensor_(cmul)(outputFrame, outputFrame, weight);
    THTensor_(cadd)(outputFrame, outputFrame, 1, bias);
  }

  THTensor_(free)(outputFrame);
  THTensor_(free)(inputWindow);
}

void THNN_(TemporalSubSampling_updateGradInput)(
          THNNState *state,
          THTensor *input,
          THTensor *gradOutput,
          THTensor *gradInput,
          THTensor *weight,
          int kW,
          int dW)
{

  THTensor *gradOutputFrame;
  THTensor *gradInputWindow, *buffer, *kwunit;
  long k;

  THArgCheck(THTensor_(isContiguous)(weight), 4, "weight must be contiguous");
  THNN_(TemporalSubSampling_shapeCheck)(state, input, gradOutput, kW, dW, NULL);

  gradOutputFrame = THTensor_(new)();
  gradInputWindow = THTensor_(new)();
  buffer = THTensor_(new)();
  kwunit = THTensor_(newWithSize1d)(kW);

  THTensor_(fill)(kwunit, 1);
  THTensor_(resizeAs)(gradInput, input);
  THTensor_(zero)(gradInput);

  for(k = 0; k < gradOutput->size[0]; k++)
  {
    THTensor_(narrow)(gradInputWindow, gradInput, 0, k*dW, kW);
    THTensor_(select)(gradOutputFrame, gradOutput, 0, k);
    THTensor_(cmul)(buffer, weight, gradOutputFrame);
    THTensor_(addr)(gradInputWindow, 1, gradInputWindow, 1, kwunit, buffer);
  }

  THTensor_(free)(gradOutputFrame);
  THTensor_(free)(gradInputWindow);
  THTensor_(free)(buffer);
  THTensor_(free)(kwunit);
}

void THNN_(TemporalSubSampling_accGradParameters)(
          THNNState *state,
          THTensor *input,
          THTensor *gradOutput,
          THTensor *gradWeight,
          THTensor *gradBias,
          int kW,
          int dW,
          accreal scale_)
{
  real scale = TH_CONVERT_ACCREAL_TO_REAL(scale_);
  THTensor *gradOutputFrame;
  THTensor *inputWindow, *buffer;
  long k;

  THNN_(TemporalSubSampling_shapeCheck)(state, input, gradOutput, kW, dW, NULL);
  gradOutputFrame = THTensor_(new)();
  inputWindow = THTensor_(new)();
  buffer = THTensor_(new)();

  for(k = 0; k < gradOutput->size[0]; k++)
  {
    THTensor_(narrow)(inputWindow, input, 0, k*dW, kW);
    THTensor_(select)(gradOutputFrame, gradOutput, 0, k);
    THTensor_(sum)(buffer, inputWindow, 0, 1);
    THTensor_(addcmul)(gradWeight, gradWeight, scale, buffer, gradOutputFrame);
    THTensor_(cadd)(gradBias, gradBias, scale, gradOutputFrame);
  }

  THTensor_(free)(gradOutputFrame);
  THTensor_(free)(inputWindow);
  THTensor_(free)(buffer);
}

#endif
