/// QasTools: Desktop toolset for the Linux sound system ALSA.
/// \copyright See COPYING file.

#include "image_allocator.hpp"
#include "image_request.hpp"
#include "image_set.hpp"
#include "image_set_group.hpp"
#include "image_set_meta.hpp"
#include "image_set_state.hpp"
#include "is_buffer.hpp"
#include "paint_job.hpp"
#include "painter.hpp"
#include "painter_thread.hpp"
#include "painter_thread_shared.hpp"
#include <cassert>
#include <iostream>

namespace dpe
{

Image_Allocator::Image_Allocator ( QObject * parent_n )
: QObject ( parent_n )
, _buffer ( std::make_unique< dpe::IS_Buffer > () )
, _shared ( std::make_shared< dpe::Painter_Thread_Shared > () )
{
  {
    const std::size_t num_threads_max = 8;
    _num_threads = qMax ( QThread::idealThreadCount (), 1 );
    _num_threads = qMin ( _num_threads, num_threads_max );
  }

  _stop_timer.setInterval ( 1500 );
  _stop_timer.setSingleShot ( true );
  connect (
      &_stop_timer, &QTimer::timeout, this, &Image_Allocator::stop_timeout );
}

Image_Allocator::~Image_Allocator ()
{
  stop_threads ();
}

void
Image_Allocator::install_painter (
    const std::shared_ptr< dpe::Painter > & painter_n )
{
  _shared->install_painter ( painter_n );
}

void
Image_Allocator::start_threads ()
{
  // Create threads
  _threads.reserve ( _num_threads );
  for ( std::size_t ii = 0; ii < _num_threads; ++ii ) {
    _threads.push_back ( std::make_unique< Painter_Thread > ( _shared ) );
  }
  // Start threads
  for ( auto & thread : _threads ) {
    thread->start ();
  }
}

void
Image_Allocator::stop_threads ()
{
  if ( !_threads.empty () ) {
    // Send abort signal
    _shared->abort_threads ( _threads.size () );
    for ( auto & thread : _threads ) {
      thread->wait ();
    }
    _threads.clear ();
  }
}

void
Image_Allocator::stop_timeout ()
{
  if ( _shared->is_idle () ) {
    stop_threads ();
  } else {
    _stop_timer.start ();
  }
}

void
Image_Allocator::send_request ( dpe::Image_Request * request_n )
{
  if ( request_n == 0 ) {
    return;
  }

  for ( std::size_t ii = 0; ii < request_n->group->img_sets.size (); ++ii ) {
    dpe::IS_Buffer_Handle * handle = _buffer->acquire_return_handle (
        request_n->meta[ ii ], request_n->group->img_sets[ ii ] );
    if ( handle == nullptr ) {
      // No matching image set found!
      // Create new handle and image set
      // and enqueue for rendering
      {
        auto hnew = std::make_unique< dpe::IS_Buffer_Handle > ();
        hnew->meta.reset ( request_n->meta[ ii ]->new_copy () );
        hnew->img_set =
            std::make_unique< dpe::Image_Set > ( hnew->meta->num_images );
        handle = hnew.get ();
        _buffer->append_handle ( std::move ( hnew ) );
      }
      // Start threads on demand
      if ( _threads.empty () ) {
        start_threads ();
        _stop_timer.start ();
      }
      // Enqueue handle
      enqueue_handle ( handle );
    }
    request_n->group->img_sets[ ii ] = handle->img_set.get ();
    request_n->states[ ii ] = handle->state.get ();
  }
}

void
Image_Allocator::enqueue_handle ( dpe::IS_Buffer_Handle * handle_n )
{
  // Increment todo counters
  handle_n->state->init_todo ( handle_n->img_set->num_images () );

  // Create paint jobs and enqueue
  std::size_t num = handle_n->img_set->num_images ();
  for ( std::size_t ii = 0; ii < num; ++ii ) {
    auto pjob = std::make_unique< dpe::Paint_Job > ();
    pjob->meta = handle_n->meta.get ();
    pjob->img_set = handle_n->img_set.get ();
    pjob->img_idx = ii;
    pjob->state = handle_n->state.get ();
    _shared->enqueue_job ( std::move ( pjob ) );
  }
}

void
Image_Allocator::return_group ( dpe::Image_Set_Group * group_n )
{
  for ( std::size_t ii = 0; ii < group_n->img_sets.size (); ++ii ) {
    _buffer->return_img_set ( group_n->img_sets[ ii ] );
    group_n->img_sets[ ii ] = nullptr;
  }
}

} // namespace dpe
