/* -*- c++ -*- */
/*
 * Copyright 2012-2013 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio
 *
 * GNU Radio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3, or (at your option)
 * any later version.
 *
 * GNU Radio 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.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gnuradio/thread/thread.h>

#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)

#include <Windows.h>

namespace gr {
  namespace thread {

    gr_thread_t
    get_current_thread_id()
    {
      return GetCurrentThread();
    }

    void
    thread_bind_to_processor(int n)
    {
      std::vector<int> mask(1, n);
      thread_bind_to_processor(get_current_thread_id(), mask);
    }

    void
    thread_bind_to_processor(const std::vector<int> &mask)
    {
      thread_bind_to_processor(get_current_thread_id(), mask);
    }

    void
    thread_bind_to_processor(gr_thread_t thread, int n)
    {
      std::vector<int> mask(1, n);
      thread_bind_to_processor(thread, mask);
    }

    void
    thread_bind_to_processor(gr_thread_t thread, const std::vector<int> &mask)
    {
      //DWORD_PTR mask = (1 << n);
      DWORD_PTR dword_mask = 0;

      std::vector<int> _mask = mask;
      std::vector<int>::iterator itr;
      for(itr = _mask.begin(); itr != _mask.end(); itr++)
        dword_mask |= (1 << (*itr));

      DWORD_PTR ret = SetThreadAffinityMask(thread, dword_mask);
      if(ret == 0) {
        std::stringstream s;
        s << "thread_bind_to_processor failed with error: " 
          << GetLastError() << std::endl;
        throw std::runtime_error(s.str());
      }
    }

    void
    thread_unbind()
    {
      thread_unbind(get_current_thread_id());
    }

    void
    thread_unbind(gr_thread_t thread)
    {
      DWORD_PTR dword_mask = sizeof(DWORD_PTR) - 1;
      DWORD_PTR ret = SetThreadAffinityMask(thread, dword_mask);
      if(ret == 0) {
        std::stringstream s;
        s << "thread_unbind failed with error: "
          << GetLastError() << std::endl;
        throw std::runtime_error(s.str());
      }
    }

    int 
    thread_priority(gr_thread_t thread)
    {
      // Not implemented on Windows
      return -1;
    }
    
    int 
    set_thread_priority(gr_thread_t thread, int priority)
    {
      // Not implemented on Windows
      return -1;
    }

  } /* namespace thread */
} /* namespace gr */


#elif defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__)

namespace gr {
  namespace thread {

    gr_thread_t
    get_current_thread_id()
    {
      // Not implemented on OSX
    }

    void
    thread_bind_to_processor(int n)
    {
      // Not implemented on OSX
    }

    void
    thread_bind_to_processor(gr_thread_t thread, int n)
    {
      // Not implemented on OSX
    }

    void
    thread_bind_to_processor(const std::vector<int> &mask)
    {
      // Not implemented on OSX
    }

    void
    thread_bind_to_processor(gr_thread_t thread, const std::vector<int> &mask)
    {
      // Not implemented on OSX
    }

    void
    thread_unbind()
    {
      // Not implemented on OSX
    }

    void
    thread_unbind(gr_thread_t thread)
    {
      // Not implemented on OSX
    }

    int 
    thread_priority(gr_thread_t thread)
    {
      // Not implemented on OSX
      return -1;
    }
    
    int 
    set_thread_priority(gr_thread_t thread, int priority)
    {
      // Not implemented on OSX
      return -1;
    }

  } /* namespace thread */
} /* namespace gr */

#else

#include <sstream>
#include <stdexcept>
#include <pthread.h>

namespace gr {
  namespace thread {

    gr_thread_t
    get_current_thread_id()
    {
      return pthread_self();
    }

    void
    thread_bind_to_processor(int n)
    {
      std::vector<int> mask(1, n);
      thread_bind_to_processor(get_current_thread_id(), mask);
    }

    void
    thread_bind_to_processor(const std::vector<int> &mask)
    {
      thread_bind_to_processor(get_current_thread_id(), mask);
    }

    void
    thread_bind_to_processor(gr_thread_t thread, int n)
    {
      std::vector<int> mask(1, n);
      thread_bind_to_processor(thread, mask);
    }

    void
    thread_bind_to_processor(gr_thread_t thread, const std::vector<int> &mask)
    {
      cpu_set_t set;
      size_t len = sizeof(cpu_set_t);
      std::vector<int> _mask = mask;
      std::vector<int>::iterator itr;

      CPU_ZERO(&set);
      for(itr = _mask.begin(); itr != _mask.end(); itr++)
        CPU_SET(*itr, &set);

      int ret = pthread_setaffinity_np(thread, len, &set);
      if(ret != 0) {
        std::stringstream s;
        s << "thread_bind_to_processor failed with error: " << ret << std::endl;
        throw std::runtime_error(s.str());
      }
    }

    void
    thread_unbind()
    {
      thread_unbind(get_current_thread_id());
    }

    void
    thread_unbind(gr_thread_t thread)
    {
      cpu_set_t set;
      size_t len = sizeof(cpu_set_t);

      CPU_ZERO(&set);
      long ncpus = sysconf(_SC_NPROCESSORS_ONLN);
      for(long n = 0; n < ncpus; n++) {
        CPU_SET(n, &set);
      }

      int ret = pthread_setaffinity_np(thread, len, &set);
      if(ret != 0) {
        std::stringstream s;
        s << "thread_unbind failed with error: " << ret << std::endl;
        throw std::runtime_error(s.str());
      }
    }

    int 
    thread_priority(gr_thread_t thread)
    {
      sched_param param;
      int priority;
      int policy;
      int ret;
      ret = pthread_getschedparam (thread, &policy, &param);
      priority = param.sched_priority;
      return (ret==0)?priority:ret;
    }
    
    int 
    set_thread_priority(gr_thread_t thread, int priority)
    {
      int policy;
      struct sched_param param;
      pthread_getschedparam (thread, &policy, &param);
      param.sched_priority = priority;
      return pthread_setschedparam(thread, policy, &param);
    }

  } /* namespace thread */
} /* namespace gr */

#endif