/*=========================================================================
 *
 *  Copyright UMC Utrecht and contributors
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
#ifndef __itkOpenCLProgram_h
#define __itkOpenCLProgram_h

#include "itkOpenCLDevice.h"
#include "itkOpenCLKernel.h"

#include <string>

namespace itk
{
/**
 * \class OpenCLProgram
 * \brief The OpenCLProgram class represents an OpenCL program object.
 * \ingroup OpenCL
 */

// Forward declaration
class OpenCLContext;

class ITKOpenCL_EXPORT OpenCLProgram
{
public:

  /** Standard class typedefs. */
  typedef OpenCLProgram Self;

  /** Constructs a null OpenCL program object. */
  OpenCLProgram();

  /** Constructs an OpenCL program object from the native identifier \a id,
   * and associates it with \a context and debug file \a fileName.
   * This class will take over ownership of \a id and will release it in the destructor. */
  OpenCLProgram( OpenCLContext * context, cl_program id,
    const std::string & fileName = std::string() );

  /** Constructs a copy of \a other. */
  OpenCLProgram( const OpenCLProgram & other );

  /** Releases this OpenCL program object.
   * If this is the last reference to the program, it will be destroyed. */
  ~OpenCLProgram();

  /** Assigns \a other to this object. */
  OpenCLProgram & operator=( const OpenCLProgram & other );

  /** Returns null if this OpenCL program object is null. */
  bool IsNull() const { return this->m_Id == 0; }

  /** Returns the OpenCL context that this program was created within. */
  OpenCLContext * GetContext() const { return this->m_Context; }

  /** Returns the native OpenCL identifier for this program. */
  cl_program GetProgramId() const { return this->m_Id; }

  /** Returns the debug filename that this program was created within. */
  std::string GetFileName() const { return this->m_FileName; }

  /** Builds this program from the sources and binaries that were supplied,
   * with extra build compiler options specified by \a extraBuildOptions.
   * The main compiler options are provided during CMake configuration
   * (see group OPENCL in CMake).
   * Returns true if the program was built; false otherwise.
   * \sa GetLog(), CreateKernel() */
  bool Build( const std::string & extraBuildOptions = std::string() );

  /** \overload
   * Builds this program from the sources and binaries that were supplied,
   * with extra build compiler options specified by \a extraBuildOptions.
   * The main compiler options are provided during CMake configuration
   * (see group OPENCL in CMake).
   * If \a devices is not empty, the program will only be built for devices
   * in the specified list. Otherwise the program will be built for all
   * devices on the program's context.
   * Returns true if the program was built; false otherwise.
   * \sa GetLog(), CreateKernel() */
  bool Build( const std::list< OpenCLDevice > & devices,
    const std::string & extraBuildOptions = std::string() );

  /** Returns the error GetLog that resulted from the last build().
   * \sa Build() */
  std::string GetLog() const;

  /** Returns the list of devices that this program is associated with.
   * \sa GetBinaries() */
  std::list< OpenCLDevice > GetDevices() const;

  /** Creates a kernel for the entry point associated with \a name
   * in this program.
   * \sa Build() */
  OpenCLKernel CreateKernel( const std::string & name ) const;

  /** Creates a list of kernels for all of the entry points in this program. */
  std::list< OpenCLKernel > CreateKernels() const;

private:

  OpenCLContext * m_Context;
  cl_program      m_Id;
  std::string     m_FileName;
};

/** Operator ==
* Returns true if \a lhs OpenCL program is the same as \a rhs, false otherwise.
* \sa operator!= */
bool ITKOpenCL_EXPORT operator==( const OpenCLProgram & lhs, const OpenCLProgram & rhs );

/** Operator !=
* Returns true if \a lhs OpenCL program is not the same as \a rhs, false otherwise.
* \sa operator== */
bool ITKOpenCL_EXPORT operator!=( const OpenCLProgram & lhs, const OpenCLProgram & rhs );

/** Stream out operator for OpenCLProgram */
template< typename charT, typename traits >
inline
std::basic_ostream< charT, traits > &
operator<<( std::basic_ostream< charT, traits > & strm,
  const OpenCLProgram & program )
{
  if( program.IsNull() )
  {
    strm << "OpenCLProgram(null)";
    return strm;
  }

  const char indent = ' ';

  strm << "OpenCLProgram" << std::endl
       << indent << "Id: " << program.GetProgramId() << std::endl;

  return strm;
}


} // end namespace itk

#endif /* __itkOpenCLProgram_h */
