// Process that takes samples and distributes them to the appropriate
// process.
void producer_thread(
  sampbuf_sync_t & sampbuf_sync,
  capbuf_sync_t & capbuf_sync,
  global_thread_data_t & global_thread_data,
  tracked_cell_list_t & tracked_cell_list,
  double & fc
) {
  global_thread_data.producer_thread_id=syscall(SYS_gettid);

  // Main loop which distributes data to the appropriate subthread.
  // Local storage for each cell.
  cell_local_t cell_local[504];
  for (uint16 t=0;t<504;t++) {
    cell_local[t].serial_num=0;
  }

  //Real_Timer tt;
  double sample_time=-1;
  bool searcher_capbuf_filling=false;
  uint32 searcher_capbuf_idx=0;
  //unsigned long long int sample_number=0;
  // Elevate privileges of the producer thread.
  //int retval=nice(-10);
  //if (retval==-1) {
  //  cerr << "Error: could not set elevated privileges" << endl;
  //  ABORT(-1);
  //}
  //int policy=SCHED_RR;
  //struct sched_param param;
  //param.sched_priority=50;
  ////pthread_getschedparam(pthread_self(), &policy, &param);
  //if (pthread_setschedparam(pthread_self(),policy,&param)) {
  //  cerr << "Error: could not elevate main thread priority" << endl;
  //  ABORT(-1);
  //}
  //tt.tic();
#define BLOCK_SIZE 10000
  while (true) {
    // Each iteration of this loop processes one block of data.
    const double frequency_offset=global_thread_data.frequency_offset();
    const double k_factor=(global_thread_data.fc_requested-frequency_offset)/global_thread_data.fc_programmed;
    //const double k_factor_inv=1/k_factor;
    const double & fs_programmed=global_thread_data.fs_programmed;

    // Get the next block
    //complex <double> sample;
    cvec samples(BLOCK_SIZE);
    vec samples_timestamp(BLOCK_SIZE);
    uint32 n_samples;
    {
      boost::mutex::scoped_lock lock(sampbuf_sync.mutex);
      while (sampbuf_sync.fifo.size()<2) {
        sampbuf_sync.condition.wait(lock);
      }
      // Dump data if there is too much in the fifo
      while (sampbuf_sync.fifo.size()>2*FS_LTE/16*1.5) {
        for (uint32 t=0;t<(unsigned)round_i(fs_programmed*k_factor);t++) {
          sampbuf_sync.fifo.pop_front();
        }
        global_thread_data.raw_seconds_dropped_inc();
      }
      n_samples=BLOCK_SIZE;
      // complex <double> sample_temp;
      for (uint16 t=0;t<BLOCK_SIZE;t++) {
        if (sampbuf_sync.fifo.size()<2) {
          n_samples=t;
          break;
        }
        // sample_temp.real()=(sampbuf_sync.fifo.front()-127.0)/128.0;
        double sample_real = (sampbuf_sync.fifo.front()-127.0)/128.0;
        sampbuf_sync.fifo.pop_front();
        // sample_temp.imag()=(sampbuf_sync.fifo.front()-127.0)/128.0;
        double sample_imag = (sampbuf_sync.fifo.front()-127.0)/128.0;
        complex <double> sample_temp(sample_real, sample_imag);

        sampbuf_sync.fifo.pop_front();
        samples(t)=sample_temp;
        sample_time+=(FS_LTE/16)/(fs_programmed*k_factor);
        //sample_time=itpp_ext::matlab_mod(sample_time,19200.0);
        if (sample_time>19200.0)
          sample_time-=19200.0;
        samples_timestamp(t)=sample_time;
      }
    }

    // Handle the searcher capture buffer
    for (uint32 t=0;t<n_samples;t++) {
      if ((capbuf_sync.request)&&(abs(WRAP(samples_timestamp(t)-0,-19200.0/2,19200.0/2))<0.5)) {
        //cout << "searcher data cap beginning" << samples_timestamp(t) << endl;
        capbuf_sync.request=false;
        searcher_capbuf_filling=true;
        searcher_capbuf_idx=0;
        capbuf_sync.late=WRAP(samples_timestamp(t)-0,-19200.0/2,19200.0/2);
      }

      // Populate the capture buffer
      if (searcher_capbuf_filling) {
        capbuf_sync.capbuf(searcher_capbuf_idx++)=samples(t);
        if (searcher_capbuf_idx==(unsigned)capbuf_sync.capbuf.size()) {
          // Buffer is full. Signal the searcher thread.
          searcher_capbuf_filling=false;
          boost::mutex::scoped_lock lock(capbuf_sync.mutex);
          capbuf_sync.condition.notify_one();
          //cout << "searcher data cap finished" << endl;
        }
      }
    }

    // Loop for each tracked cell and save data, if necessary. Also delete
    // threads that may have lost lock.
    {
      boost::mutex::scoped_lock lock(tracked_cell_list.mutex);
      list <tracked_cell_t *>::iterator it=tracked_cell_list.tracked_cells.begin();
      while (it!=tracked_cell_list.tracked_cells.end()) {
        tracked_cell_t & tracked_cell=(*(*it));
        // See if this thread has been launched yet. If not, launch it.
        if (!tracked_cell.launched) {
          tracked_cell.thread=boost::thread(tracker_thread,boost::ref(tracked_cell),boost::ref(global_thread_data));
          tracked_cell.launched=true;
        }
        double frame_timing=tracked_cell.frame_timing();

        cell_local_t & cl=cell_local[tracked_cell.n_id_cell];

        // Initialize local storage if necessary
        if (tracked_cell.serial_num!=cl.serial_num) {
          cl.serial_num=tracked_cell.serial_num;
          cl.pdu.slot_num=0;
          cl.pdu.sym_num=0;
          cl.target_cap_start_time=(tracked_cell.cp_type==cp_type_t::NORMAL)?10:32;
          cl.filling=false;
          cl.buffer_offset=0;
          if (cl.serial_num==1)
            cl.pdu.data.set_size(128);
        }

        // Delete the tracker if lock has been lost.
        if (tracked_cell.kill_me) {
          tracked_cell_t * temp=(*it);
          it=tracked_cell_list.tracked_cells.erase(it);
          delete temp;
          continue;
        }

        // Loop for each sample in the buffer.
        for (uint32 t=0;t<n_samples;t++) {
          // See if we should start filling the buffer.
          if (tracked_cell.tracker_thread_ready&&!cl.filling) {
            double tdiff=WRAP(samples_timestamp(t)-(frame_timing+cl.target_cap_start_time),-19200.0/2,19200.0/2);
            if (
              // Ideal start time is 0.5 samples away from current time
              (abs(tdiff)<0.5) ||
              // It's possible for the frame timing to change between iterations
              // of the outer loop and because of this, it's possible that
              // we missed the best start. Start capturing anyways.
              ((tdiff>0)&&(tdiff<3))
            ) {
              // Configure parameters for this capture
              cl.filling=true;
              cl.pdu.late=tdiff;
              cl.buffer_offset=0;
              // Record the frequency offset and frame timing as they were
              // at the beginning of the capture.
              cl.pdu.frequency_offset=frequency_offset;
              cl.pdu.frame_timing=frame_timing;
            }
          }

          // Save this sample if our state indicates we are filling the
          // buffer.
          if (cl.filling) {
            cl.pdu.data(cl.buffer_offset++)=samples(t);
            if (cl.buffer_offset==128) {
              // Buffer is full! Send PDU
              {
                boost::mutex::scoped_lock lock2(tracked_cell.fifo_mutex);
                tracked_cell.fifo.push(cl.pdu);
                tracked_cell.fifo_peak_size=MAX(tracked_cell.fifo.size(),tracked_cell.fifo_peak_size);
                tracked_cell.fifo_condition.notify_one();
              }
              //cout << "fifo size: " << tracked_cell.fifo.size() << endl;
              // Calculate trigger parameters of next capture
              cl.filling=false;
              if (tracked_cell.cp_type==cp_type_t::EXTENDED) {
                cl.target_cap_start_time+=32+128;
              } else {
                cl.target_cap_start_time+=(cl.pdu.sym_num==6)?128+10:128+9;
              }
              cl.target_cap_start_time=mod(cl.target_cap_start_time,19200);
              slot_sym_inc(tracked_cell.n_symb_dl(),cl.pdu.slot_num,cl.pdu.sym_num);
            }
          }
        }
        ++it;
      }
    }
  }
}