// file: $isip_ifc/class/algo/Window/win_05.cc
// version: $Id: win_05.cc 10482 2006-03-14 21:51:09Z srinivas $
//

// isip include files
//
#include "Window.h"

// method: apply
//
// arguments:
//  Vector<AlgorithmData>& output: (output) output data
//  const Vector< CircularBuffer<AlgorithmData> >& input: (input) input data
//
// return: a boolean value indicating status
//
// this method calls the appropriate computation methods
//
boolean Window::apply(Vector<AlgorithmData>& output_a,
		      const Vector< CircularBuffer<AlgorithmData> >& input_a) {

  // determine the number of input channels and force the output to be
  // that number
  //
  long len = input_a.length();
  output_a.setLength(len);
  boolean res = true;

  // start the debugging output
  //
  displayStart(this);
  
  // loop over the channels and call the compute method
  //
  for (long c = 0; c < len; c++) {

    // display the channel number
    //
    displayChannel(c);
    
    // call AlgorithmData::makeVectorFloat to force the output vector for
    // this channel to be a VectorFloat, use the CircularBuffer<AlgorithmData>
    // directly on the input for this channel
    //          
    res &= compute(output_a(c).makeVectorFloat(),
		   input_a(c),
		   input_a(c)(0).getCoefType(),
		   c);
    
    // set the coefficient type for the output
    //            
    output_a(c).setCoefType(input_a(c)(0).getCoefType());
  }

  // finish the debugging output
  //
  displayFinish(this);
  
  // exit gracefully
  //
  return res;
}

// method: compute
//
// arguments:
//  VectorFloat& vector_out: (output) operand
//  const VectorFloat& vector_in: (input) operand
//  AlgorithmData::COEF_TYPE coef_type: (input) coeff type of input signal
//  long channel_index: (input) the channel index of input signal
//
// return: a boolean value containing status
//
// this method applies the specified window on the input vector
//
boolean Window::compute(VectorFloat& vector_out_a,
			const VectorFloat& vector_in_a,
			AlgorithmData::COEF_TYPE coef_type_a,
			long channel_index_a) {

  // declare local variables
  //
  boolean status = false;

  // check whether we need to initialize
  //
  if (!is_valid_d) {
    init();
  }

  // algorithm: *all* standard algorithms
  //
  if ((algorithm_d == RECTANGULAR) ||
      (algorithm_d == BARTLETT) ||
      (algorithm_d == BLACKMAN) ||
      (algorithm_d == DOLPH_CHEBYSHEV) ||
      (algorithm_d == GAUSSIAN) ||
      (algorithm_d == HAMMING) ||
      (algorithm_d == HANNING) ||
      (algorithm_d == KAISER) ||
      (algorithm_d == LIFTER) ||
      (algorithm_d == CUSTOM)) {
    status = computeCommon(vector_out_a, vector_in_a);
  }
  
  // error: unknown algorithm
  //
  else {
    status = Error::handle(name(), L"compute", ERR_UNKALG,
			   __FILE__, __LINE__);
  }

  // possibly display the data
  //
  display(vector_out_a, vector_in_a, name());  
  
  // exit gracefully
  //
  return status;
}

// method: compute
//
// arguments:
//  VectorComplexFloat& vector_out: (output) operand
//  const VectorComplexFloat& vector_in: (input) operand
//  AlgorithmData::COEF_TYPE coef_type: (input) coeff type of input signal
//  long channel_index: (input) the channel index of input signal
//
// return: a boolean value containing status
//
// this method applies the specified window on the input vector
//
boolean Window::compute(VectorComplexFloat& vector_out_a,
			const VectorComplexFloat& vector_in_a,
			AlgorithmData::COEF_TYPE coef_type_a,
			long channel_index_a) {

  // declare local variables
  //
  boolean status = false;

  // check whether we need to initialize
  //
  if (!is_valid_d) {
    init();
  }

  // algorithm: *all* standard algorithms
  //
  if ((algorithm_d == RECTANGULAR) ||
      (algorithm_d == BARTLETT) ||
      (algorithm_d == BLACKMAN) ||
      (algorithm_d == DOLPH_CHEBYSHEV) ||
      (algorithm_d == GAUSSIAN) ||
      (algorithm_d == HAMMING) ||
      (algorithm_d == HANNING) ||
      (algorithm_d == KAISER) ||
      (algorithm_d == LIFTER) ||
      (algorithm_d == CUSTOM)) {
    status = computeCommon(vector_out_a, vector_in_a);
  }
  
  // error: unknown algorithm
  //
  else {
    status = Error::handle(name(), L"compute", ERR_UNKALG,
			   __FILE__, __LINE__);
  }

  // possibly display the data
  //
  display(vector_out_a, vector_in_a, name());  
  
  // exit gracefully
  //
  return status;
}


// method: compute
//
// arguments:
//  VectorFloat& output: (output) output data
//  const CircularBuffer<AlgorithmData>& input: (input) input data
//  AlgorithmData::COEF_TYPE coef_type: (input) coeff type of input signal
//  long channel_index: (input) the channel index of input signal
//
// return: a boolean value containing status
//
// this method applies the specified window on the input channel
//
boolean Window::compute(VectorFloat& output_a,
			const CircularBuffer<AlgorithmData>& input_a,
			AlgorithmData::COEF_TYPE coef_type_a,
			long channel_index_a) {

  // for FRAME_INTERNAL mode, we just process what data we have
  //
  if (cmode_d == FRAME_INTERNAL) {
    return compute(output_a, input_a(0).getVectorFloat(), coef_type_a,
		   channel_index_a);
  }

  float win_nsamp = duration_d * sample_freq_d;

  // compute the number of sampled in the frame from the input vector
  // size. Note that this may lead to a different number of samples in
  // the frame than the one computed by multiplying the sampling
  // frequency and the frame duration.
  //
  float frame_nsamp = (float)input_a(0).getVectorFloat().length();

  if (win_nsamp < frame_nsamp) { 
    
    VectorFloat buf;
    
    if (alignment_d == LEFT) {
      
      // because window size is smaller than frame size, just moving
      // the specified number of elements via the move method
      //
      buf.move(input_a(0).getVectorFloat(),
	       (long)Integral::round(win_nsamp),
	       0,
	       0);
      
      // apply the window function
      //
      return compute(output_a, buf, coef_type_a, channel_index_a);
    }
    
    else if (alignment_d == RIGHT) {
      
      // because window size is smaller than frame size, just moving
      // the specified number of elements via the move method
      //
      buf.move(input_a(0).getVectorFloat(),
	       (long)Integral::round(win_nsamp),
	       (long)Integral::round(frame_nsamp - win_nsamp),
	       0);
      
      // apply the window function
      //
      return compute(output_a, buf, coef_type_a, channel_index_a);
      
    }
    
    else if (alignment_d == CENTER) {
      
      // moving equally forward and backward
      //
      // we will copy the middle (current) frame no matter what, so
      // subtract it out from the totals. we will use floor for the
      // left calculation which will bias us by at most half a sample
      // into the past leaving the total number of samples unchanged.
      //
      long left_nsamp
	= (long)Integral::floor(frame_nsamp / 2.0 - win_nsamp / 2.0 + 0.0001);
      
      buf.move(input_a(0).getVectorFloat(),
	       (long)Integral::round(win_nsamp),
	       left_nsamp,
	       0);
      
      // apply the window function
      //
      return compute(output_a, buf, coef_type_a, channel_index_a);
    }
    
    // error
    //
    return Error::handle(name(), L"compute", Error::NOT_IMPLEM,
			 __FILE__, __LINE__);    
  }
  
  
  // make sure there is enough data
  //
  if ((input_a.getNumForward() < getLeadingPad())
      || (input_a.getNumBackward() < getTrailingPad())) {
    return Error::handle(name(), L"compute", ERR, __FILE__, __LINE__);
  }

  // if window size is almost equal to frame size using FRAME_INTERNAL
  // mode
  //
  if (Integral::almostEqual(win_nsamp, frame_nsamp)) {
    return compute(output_a,
		   input_a(0).getVectorFloat(),
		   coef_type_a,
		   channel_index_a);      
  }

  
  VectorFloat buf;

  if (alignment_d == LEFT) {

    long frame = 0;
    float num_left = win_nsamp;
    
    while (num_left > 0) {

      // while there are still full frames to copy, use concat
      //
      if (num_left > frame_nsamp) {
	buf.concat(input_a(frame).getVectorFloat());
	num_left -= frame_nsamp;
      }

      // for the last frame only copy over the specified number of
      // elements via the move method
      //
      else {

	// check if we have at least one sample left to move
	//
	long n_l = (long)Integral::round(num_left);
	if (n_l == 0) {
	  break;
	}

	buf.move(input_a(frame).getVectorFloat(),
		 n_l,
		 0,
		 buf.length());
	num_left = 0;
	}
      
      frame++;
    }

    // apply the window function
    //
    return compute(output_a, buf, coef_type_a, channel_index_a);
  }
  else if (alignment_d == RIGHT) {

    Float num_frames = win_nsamp / frame_nsamp;
    long frame = (long)Integral::ceil(num_frames);
    long index = frame - 1;

    // this is the number of samples not included from the first frame
    //
    long first_offset = (long)Integral::round(frame * frame_nsamp - win_nsamp);
    long first_nelem = (long)Integral::round(frame_nsamp - first_offset);
    float num_left = win_nsamp;

    boolean initialization = true;
    
    while ((long)num_left > 0) {

      // for the first frame we probably don't copy all samples, just
      // a right-aligned pre-specified number of samples.
      //
      if (initialization) {
	buf.move(input_a(-index).getVectorFloat(),
		 first_nelem, first_offset, 0);
	num_left -= first_nelem;
	initialization = false;
      }

      // for all other frames we copy the entire frame
      //
      else {
	buf.concat(input_a(-index).getVectorFloat());
	num_left -= frame_nsamp;
      }
      index--;

    }
    
    // apply the window function
    //
    return compute(output_a, buf, coef_type_a, channel_index_a);
  }

  else if (alignment_d == CENTER) {

    long num_frames = (long)Integral::ceil(win_nsamp / frame_nsamp);

    // case 0: a window is less than or equal to the frame size, only
    // copy from one the middle of the current frame
    //
    if (num_frames == 1) {
      
      long start_offset
	= (long)Integral::floor((frame_nsamp / 2.0) - (win_nsamp / 2.0));

      buf.move(input_a(0).getVectorFloat(), (long)Integral::ceil(win_nsamp),
	       start_offset, 0);
    }

    // else we must span frames -- moving equally forward and backward
    //
    else {

      // we will copy the middle (current) frame no matter what, so
      // subtract it out from the totals. we will use floor for the
      // left calculation which will bias us by at most half a sample
      // into the past leaving the total number of samples unchanged.
      //
      long left_nsamp
	= (long)Integral::floor(win_nsamp / 2.0 - frame_nsamp / 2.0);
      long right_nsamp
	= (long)Integral::ceil(win_nsamp / 2.0 - frame_nsamp / 2.0);

      long first_frame = (long)Integral::ceil(left_nsamp / frame_nsamp);

      long first_offset = first_frame * (long)Integral::round(frame_nsamp)
	- left_nsamp;
      
      long first_nelem = (long)Integral::round(frame_nsamp) - first_offset;

      for (long index = first_frame; left_nsamp > 0; ) {

	if (index == first_frame) {
	  buf.move(input_a(-index).getVectorFloat(), first_nelem,
		   first_offset, 0);
	  left_nsamp -= first_nelem;
	}
	else {
	  buf.concat(input_a(-index).getVectorFloat());
	  left_nsamp -= (long)Integral::round(frame_nsamp);
	}
      }

      // copy the middle frame
      //
      buf.concat(input_a(0).getVectorFloat());

      long index = 1;
      
      // now copy the future frames
      //
      while (right_nsamp > 0) {

	// while there are still full frames to copy, use concat
	//
	if (right_nsamp > frame_nsamp) {
	  buf.concat(input_a(index).getVectorFloat());
	  right_nsamp -= (long)Integral::round(frame_nsamp);
	}

	// for the last frame only copy over the specified number of
	// elements via the move method
	//
	else {
	  buf.move(input_a(index).getVectorFloat(),
		   (long)Integral::ceil(right_nsamp), 0,
		   buf.length());
	  right_nsamp = 0;
	}
	index++;
      }
    }

    // apply the window function
    //
    return compute(output_a, buf, coef_type_a, channel_index_a);
  }

  // error
  //
  return Error::handle(name(), L"compute", Error::NOT_IMPLEM,
		       __FILE__, __LINE__);
}


// method: computeCommon
//
// arguments:
//  VectorFloat& vector_out: (output) operand
//  const VectorFloat& vector_in: (input) operand
//
// return: a boolean value containing status
//
// this method applies the window on the input vector and is called by the
// public compute method
//
boolean Window::computeCommon(VectorFloat& vector_out_a,
			      const VectorFloat& vector_in_a) {

  // set the size of the window
  //
  long len = vector_in_a.length();
  setSize(len);
  vector_out_a.setLength(len);

  // if window is not set then initialize the window
  //
  if (!is_valid_d) {
    init();
  }

  // now apply
  //
  for (long i = 0; i < len; i++) {
    vector_out_a(i) = data_d(i) * vector_in_a(i);
  }
  
  // exit gracefully
  //
  return true;
}

// method: computeCommon
//
// arguments:
//  VectorComplexFloat& vector_out: (output) operand
//  const VectorComplexFloat& vector_in: (input) operand
//
// return: a boolean value containing status
//
// this method applies the window on the input vector and is called by the
// public compute method
//
boolean Window::computeCommon(VectorComplexFloat& vector_out_a,
			      const VectorComplexFloat& vector_in_a) {

  // set the size of the window
  //
  long len = vector_in_a.length();
  setSize(len);
  vector_out_a.setLength(len);

  // if window is not set then initialize the window
  //
  if (!is_valid_d) {
    init();
  }

  // now apply
  //
  for (long i = 0; i < len; i++) {
    vector_out_a(i) = complexfloat((float)data_d(i) *
				   (float)vector_in_a(i).real(),
				   (float)data_d(i) *
				   (float)vector_in_a(i).imag());
  }
  
  // exit gracefully
  //
  return true;
}
