
// SPDX-License-Identifier: CC-BY-NC-SA-4.0
//
// Copyright (C) 2026 Bit by Bit Signal Processing LLC (https://bxbsp.com)
//
// This work is placed under the "Creative Commons Attribution
// NonCommercial ShareAlike 4.0 International" license, known
// by the shortened acronym "CC-BY-NC-SA-4.0".
//
// This work is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// A CC-BY-NC-SA-4.0 license allows you to use this work for
// noncommercial purposes so long as attribution is made to the
// original author.  Modified versions of this work may be distributed,
// but only under the same license.  For further details, see the
// Creative Commons License "CC-BY-NC-SA-4.0".
//
// You should have received a copy of the CC-BY-NC-SA-4.0 license
// along with this work. If not, see
// <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
//
//

#ifndef SYNCHRONIZED_DATA_HH
#define SYNCHRONIZED_DATA_HH

#include <pthread.h>

//
// How to use:
//
// 1. lock() it
// 2. access data
// 3. if you don't like that data, wait_for_change(), go back to 2
// 4. do something with the data
// 5. unlock() it
//

template<class T> class synchronized_data
{
  pthread_mutex_t data_mutex;
  pthread_cond_t  data_condition;

  public:

// Need to lock before accessing -- no way to enforce this.
  T data;

  void lock()   
  { 
    //printf("lock %p\n", this);
    pthread_mutex_lock(&data_mutex); 
  }

  void unlock() 
  { 
    //printf("unlock %p\n", this);
    pthread_cond_signal(&data_condition); 
    pthread_mutex_unlock(&data_mutex); 
  }

  // call wait_for_change() if you don't like the value and want another thread to change it.  This
  // is called when the data is locked.  It releases the data and wakes up when the data has changed
  // and the lock has been reaquired.
  void wait_for_change()
  {
    //printf("wait_for_change %p\n", this);
    pthread_cond_wait(&data_condition, &data_mutex);
    //printf("wait_for_change release %p\n", this);
  }

  //
  // return should be either 0 or ETIMEDOUT
  //
  int wait_for_change(long sec, long nsec)
  {
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    ts.tv_sec += sec;
    ts.tv_nsec += nsec;
    int error = pthread_cond_timedwait(&data_condition, &data_mutex, &ts);
    return error;
  }
  
  synchronized_data()
    {
      //printf("create %p\n", this);
      pthread_mutex_init(&data_mutex, NULL);
      pthread_cond_init(&data_condition, NULL);
    }

  ~synchronized_data()
  {
    // If this is being destroyed in one thread and waited for in another, make sure they wait
    // is canceled before the destroy.
    //printf("delete %p\n", this);
    pthread_mutex_trylock(&data_mutex);  // lock it if not already locked.  If already locked, doesn't lock it.
    pthread_mutex_unlock(&data_mutex); 
    pthread_cond_signal(&data_condition);    
  }
};

#endif
