/*=========================================================================

 medInria

 Copyright (c) INRIA 2013 - 2018. All rights reserved.
 See LICENSE.txt for details.
 
  This software is distributed WITHOUT ANY WARRANTY; without even
  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  PURPOSE.

=========================================================================*/

#include <itkMultiThreadedImageIOBase.h>

#include <itksys/SystemTools.hxx>
#include <itksys/Directory.hxx>

namespace itk
{
  itkEventMacroDefinition(SliceReadEvent, itk::AnyEvent);

  MultiThreadedImageIOBase::MultiThreadedImageIOBase() :
    m_NumberOfThreads(0)
  {
    m_MultiThreaderBase = MultiThreaderBase::New();
    this->SetNumberOfThreads ( m_MultiThreaderBase->GetNumberOfWorkUnits() );
  }

  
  void MultiThreadedImageIOBase::AddFileName (const char* filename)
  {
    m_FileNames.push_back ( filename );
    this->SetFileName ( filename );
  }

  
  void MultiThreadedImageIOBase::SetFileNames(FileNameVectorType filenames)
  {
      unsigned int fileCount = filenames.size();
      if (fileCount)
      {
          this->SetFileName(filenames[0].c_str()); // tells the reader not to exit
          if (fileCount == 1)
          { 
              // special case when only one file is set
              // it can be a directory - we scan it for files
              if (itksys::SystemTools::FileIsDirectory(filenames[0].c_str()))
              {
                  m_FileNames.clear();
                  itksys::Directory directory;
                  directory.Load(filenames[0].c_str());
                  for (unsigned long i = 0; i < directory.GetNumberOfFiles(); i++)
                  {
                      std::string name = directory.GetPath();
                      name += directory.GetFile(i);
                      if (this->CanReadFile(name.c_str()))
                      {
                          m_FileNames.push_back(name);
                      }
                  }
              }
              else
              {
                  m_FileNames = filenames;
              }
          }
          else
          {
              m_FileNames = filenames;
          }              
      }
  }

  
  void MultiThreadedImageIOBase::Read (void* buffer)
  {
    // Set up the multithreaded processing
    ThreadStruct str;
    str.Reader = this;
    str.Buffer = buffer;
    
    this->GetMultiThreaderBase()->SetNumberOfWorkUnits( this->GetNumberOfThreads() );
    this->GetMultiThreaderBase()->SetSingleMethod   ( this->ThreaderCallback, &str );

    itkDebugMacro (<< "Executing with " << this->GetMultiThreaderBase()->GetNumberOfWorkUnits() << " threads.\n");
    // multithread the execution
    this->GetMultiThreaderBase()->SingleMethodExecute();
  }
  

  unsigned int MultiThreadedImageIOBase::SplitRequestedRegion (unsigned int id, unsigned int total, RegionType& region)
  {
    int fileCount       = (int)( m_FileNames.size() );
    int threadFileCount = (int)::ceil( fileCount/(double)total );
    
    RegionType::IndexType start;
    start[0] = id * threadFileCount;
    RegionType::SizeType length;
    length[0] = threadFileCount;
    
    unsigned int maxThreadInUse = (unsigned int)::ceil(fileCount/(double)threadFileCount) - 1;
    
    if( id == maxThreadInUse )
      length[0] = fileCount - start[0];

    region.SetIndex (start);
    region.SetSize (length);
    
    return maxThreadInUse+1;
  }

  
  ITK_THREAD_RETURN_TYPE MultiThreadedImageIOBase::ThreaderCallback( void *arg )
  {
    ThreadStruct *str;
    int total, threadId, threadCount;
    
    threadId    = ((MultiThreaderBase::WorkUnitInfo *)(arg))->WorkUnitID;
    threadCount = ((MultiThreaderBase::WorkUnitInfo *)(arg))->NumberOfWorkUnits;
	  
    str = (ThreadStruct *)(((MultiThreaderBase::WorkUnitInfo *)(arg))->UserData);
    
    RegionType region;
    total = str->Reader->SplitRequestedRegion (threadId, threadCount, region);
    
    if ( threadId < total )
    {
      str->Reader->ThreadedRead(str->Buffer, region, threadId);
    }
    // else
    //   {
    //   otherwise don't use this thread. Sometimes the threads dont
    //   break up very well and it is just as efficient to leave a 
    //   few threads idle.
    //   }
    
    return ITK_THREAD_RETURN_DEFAULT_VALUE;
  }
  
  
} // end of namespace
