/**
 * @file
 * $Revision: 1.4 $
 * $Date: 2008/05/14 21:07:11 $
 *
 *   Unless noted otherwise, the portions of Isis written by the USGS are public
 *   domain. See individual third-party library and package descriptions for
 *   intellectual property information,user agreements, and related information.
 *
 *   Although Isis has been used by the USGS, no warranty, expressed or implied,
 *   is made by the USGS as to the accuracy and functioning of such software
 *   and related material nor shall the fact of distribution constitute any such
 *   warranty, and no responsibility is assumed by the USGS in connection
 *   therewith.
 *
 *   For additional information, launch
 *   $ISISROOT/doc//documents/Disclaimers/Disclaimers.html in a browser or see
 *   the Privacy &amp; Disclaimers page on the Isis website,
 *   http://isis.astrogeology.usgs.gov, and the USGS privacy and disclaimers on
 *   http://www.usgs.gov/privacy.html.
 */

#include "ProcessByBrick.h"

#include "Brick.h"
#include "Cube.h"

using namespace std;

namespace Isis {
  ProcessByBrick::ProcessByBrick() {
    p_inputBrickSamples.clear();
    p_inputBrickLines.clear();
    p_inputBrickBands.clear();

    p_outputBrickSamples.clear();
    p_outputBrickLines.clear();
    p_outputBrickBands.clear();

    p_outputRequirements = 0;
    p_inputBrickSizeSet = false;
    p_outputBrickSizeSet = false;
    p_wrapOption = false;
    p_reverse = false;
  }




  ProcessByBrick::~ProcessByBrick() {
  }


  /**
   * Opens an input cube specified by the user and verifies requirements are
   * met. This method is overloaded and adds the requirements of
   * ISIS::SpatialMatch which requires all input cubes to have the same
   * dimensions or be exactly be exactly one.  For more information see
   * Process::SetInputCube
   *
   *  @return Cube*
   *
   * @param parameter User parameter to obtain file to open. Typically, the
   * value is "FROM". For example, the user can specify on the command line
   * FROM=myfile.cub and this method will attempt to open the cube "myfile.cub"
   * if the parameter was set to "FROM".
   *
   * @param requirements See Process::SetInputCube for more information.
   *                     Defaults to 0
   *
   * @throws iException::Message
   */
  Cube *ProcessByBrick::SetInputCube(const QString &parameter,
                                     int requirements) {
    int allRequirements = AllMatchOrOne;
    allRequirements |= requirements;
    return Process::SetInputCube(parameter, allRequirements);
  }

  /**
    * Opens an input cube specified by the user, sets the attributes, and
    * verifies requirements are met. This method is overloaded and adds the
    * requirements of ISIS::SpatialMatch which requires all input cubes to
    * have the same dimensions or be exactly be exactly one.  For more
    * information see Process::SetInputCube
    *
    *  @return Cube*
    *
    * @param file User parameter to obtain file to open. Typically, the
    * value is "FROM". For example, the user can specify on the command
    * line FROM=myfile.cub and this method will attempt to open
    * the cube "myfile.cub" if the parameter was set to "FROM".
    *
    * @param att See Process::SetInputCube for more information.
    *
    * @param requirements See Process::SetInputCube for more information.
    *                     Defaults to 0
    *
    * @throws iException::Message
    */
  Cube *ProcessByBrick::SetInputCube(const QString &file,
                                     const CubeAttributeInput &att,
                                     int requirements) {
    int allRequirements = AllMatchOrOne;
    allRequirements |= requirements;
    return Process::SetInputCube(file, att, allRequirements);
  }


void ProcessByBrick::SetOutputRequirements(int outputRequirements) {

  p_outputRequirements = outputRequirements;


}

  void ProcessByBrick::SetBricks(IOCubes cn){

      ;

  }

  /**
    *  Verifies the dimensions of the input/output cubes.
    *
    * @param cn  An IOCubes enumeration for one of three possible Input/Output
    * situations:
    *     InPlace:          The input cube is the output cube
    *     InputOutput:      One input cube and one output cube
    *     InputOutputList:  A vector of input and output cubes.  The input vector
    *                       is not necessarily the same length as the output vector
    *
    * @throws iException::Message
    */
    void ProcessByBrick::VerifyCubes(IOCubes cn){

      switch(cn){

           //Error check
            case InPlace:

              if (InputCubes.size() +OutputCubes.size() > 1) {
                string m = "You can only specify exactly one input or output cube";
                throw IException(IException::Programmer,m,_FILEINFO_);
              }
              else if ( (InputCubes.size() + OutputCubes.size() == 0) ){

                  string m = "You haven't specified an input or output cube";
                       throw IException(IException::Programmer, m, _FILEINFO_);
              }

              break;

            case InputOutput:

              //Error checks ... there must be one input and output
              if(InputCubes.size() != 1) {
                    string m = "You must specify exactly one input cube";
                    throw IException(IException::Programmer, m, _FILEINFO_);
                  }
                  else if(OutputCubes.size() != 1) {
                    string m = "You must specify exactly one output cube";
                    throw IException(IException::Programmer, m, _FILEINFO_);
                  }

              // The lines in the input and output must match
               if(InputCubes[0]->lineCount() != OutputCubes[0]->lineCount()) {
                   string m = "The number of lines in the input and output cubes ";
                   m += "must match";
                   throw IException(IException::Programmer, m, _FILEINFO_);
                 }

               if(InputCubes[0]->sampleCount() != OutputCubes[0]->sampleCount()) {
                   string m = "The number of samples in the input and output cubes ";
                   m += "must match";
                   throw IException(IException::Programmer, m, _FILEINFO_);
                 }


                 // The bands in the input and output must match

               //If we are only looking for a spatial match (just match lines/samples)
               //but not bands, then we skip over this check.

               if ( !(p_outputRequirements & Isis::SpatialMatch ) ) {
               if(InputCubes[0]->bandCount() != OutputCubes[0]->bandCount()) {
                   string m = "The number of bands in the input and output cubes ";
                   m += "must match";
                   throw IException(IException::Programmer, m, _FILEINFO_);
                 }

              }

              break;

            case InputOutputList:

              // Make sure we had an image
              if (InputCubes.size() + OutputCubes.size() < 1) {
                string m = "You have not specified any input or output cubes";
                throw IException(IException::Programmer, m, _FILEINFO_);
              }

              for (unsigned int i = 0; i < OutputCubes.size(); i++) {
                    if (OutputCubes[i]->lineCount() != OutputCubes[0]->lineCount() ) {
                      string m = "All output cubes must have the same number of lines ";
                      m += "as the first input cube or output cube";
                      throw IException(IException::Programmer, m, _FILEINFO_);
                    }
                   //If we are only looking for a spatial match (just match lines/samples)
                   //but not bands, then we skip over this check.
                   if ( !(p_outputRequirements & Isis::SpatialMatch ) ) {
                    if (OutputCubes[i]->bandCount() != OutputCubes[0]->bandCount() ) {
                      string m = "All output cubes must have the same number of bands ";
                      m += "as the first input cube or output cube";
                      throw IException(IException::Programmer, m, _FILEINFO_);
                    }
                    }
                  }

              break;


          }//end switch

  }



  /**
  * Sets the input and output bricks sizes to the given number of samples, 
  * lines, and bands. 
  *
  * @param ns Number of samples
  *
  * @param nl Number of lines
  *
  * @param nb Number of bands
  */
 void ProcessByBrick::SetBrickSize(int ns, int nl, int nb) {
    SetInputBrickSize(ns, nl, nb);
    SetOutputBrickSize(ns, nl, nb);
    return;
  }


  /**
   * Sets the size of all input bricks
   *
   * @param ns Number of samples
   *
   * @param nl Number of lines
   *
   * @param nb Number of bands
   */
  void ProcessByBrick::SetInputBrickSize(int ns, int nl, int nb) {
    p_inputBrickSamples.clear();
    p_inputBrickSamples.resize(InputCubes.size() + 1, ns);
    p_inputBrickLines.clear();
    p_inputBrickLines.resize(InputCubes.size() + 1, nl);
    p_inputBrickBands.clear();
    p_inputBrickBands.resize(InputCubes.size() + 1, nb);

    p_inputBrickSizeSet = true;
  }


  /**
   * Sets the brick size of the specified cube
   *
   * @param ns Number of samples
   *
   * @param nl Number of lines
   *
   * @param nb Number of bands
   *
   * @param cube The index of the cube
   */
  void ProcessByBrick::SetInputBrickSize(int ns, int nl, int nb, int cube) {
    if (cube > (int)InputCubes.size()) {
      string m = "The specified cube is out of range";
      throw IException(IException::Programmer, m, _FILEINFO_);
    }

    // If a default size has already been set, use it to fill in
    if (p_inputBrickSamples.size() > 0) {
      p_inputBrickSamples.resize(InputCubes.size() + 1, p_inputBrickSamples[0]);
      p_inputBrickLines.resize(InputCubes.size() + 1, p_inputBrickLines[0]);
      p_inputBrickBands.resize(InputCubes.size() + 1, p_inputBrickBands[0]);
    }
    //  otherwise, make this the default size
    else {
      p_inputBrickSamples.resize(InputCubes.size() + 1, ns);
      p_inputBrickLines.resize(InputCubes.size() + 1, nl);
      p_inputBrickBands.resize(InputCubes.size() + 1, nb);
    }

    p_inputBrickSamples[cube] = ns;
    p_inputBrickLines[cube] = nl;
    p_inputBrickBands[cube] = nb;

    p_inputBrickSizeSet = true;
  }


  /**
   * Sets the size of all output bricks
   *
   * @param ns Number of samples
   *
   * @param nl Number of lines
   *
   * @param nb Number of bands
   */
  void ProcessByBrick::SetOutputBrickSize(int ns, int nl, int nb) {
    p_outputBrickSamples.clear();
    p_outputBrickSamples.resize(OutputCubes.size() + 1, ns);
    p_outputBrickLines.clear();
    p_outputBrickLines.resize(OutputCubes.size() + 1, nl);
    p_outputBrickBands.clear();
    p_outputBrickBands.resize(OutputCubes.size() + 1, nb);

    p_outputBrickSizeSet = true;
  }


  /**
   * Sets the brick size of the specified output cube
   *
   * @param ns Number of samples
   *
   * @param nl Number of lines
   *
   * @param nb Number of bands
   *
   * @param cube The index of the cube
   */
  void ProcessByBrick::SetOutputBrickSize(int ns, int nl, int nb, int cube) {
    if(cube > (int)OutputCubes.size()) {
      string m = "The specified cube is out of range";
      throw IException(IException::Programmer, m, _FILEINFO_);
    }

    // If a default size has already been set, use it to fill in
    if(p_outputBrickSamples.size() > 0) {
      p_outputBrickSamples.resize(OutputCubes.size() + 1,
                                  p_outputBrickSamples[0]);
      p_outputBrickLines.resize(OutputCubes.size() + 1, p_outputBrickLines[0]);
      p_outputBrickBands.resize(OutputCubes.size() + 1, p_outputBrickBands[0]);
    }
    //  otherwise, make this the default size
    else {
      p_outputBrickSamples.resize(OutputCubes.size() + 1, ns);
      p_outputBrickLines.resize(OutputCubes.size() + 1, nl);
      p_outputBrickBands.resize(OutputCubes.size() + 1, nb);
    }

    p_outputBrickSamples[cube] = ns;
    p_outputBrickLines[cube] = nl;
    p_outputBrickBands[cube] = nb;

    p_outputBrickSizeSet = true;
  }

  /**
   * Create the output file. This method assumes that the output cube size 
   * matches the input cube size. Therefore, SetInputCube() must be called 
   * before this method. 
   *
   * @param parameter The output file name.
   *
   * @param att An output cube attribute to define the characteristics of the
   *            output cube.
   *
   * @return @b Isis::Cube Output cube.
   * @throws Isis::iException::Message "File is not in a supported
   *             organization."
   */
  Isis::Cube *ProcessByBrick::SetOutputCube(const QString &fname,
                                     const Isis::CubeAttributeOutput &att) {
    int nl = InputCubes[0]->lineCount();
    int ns = InputCubes[0]->sampleCount();
    int nb = InputCubes[0]->bandCount();
    return Process::SetOutputCube(fname, att, ns, nl, nb);
  }

  /**
   * Set the direction the data will be read, either all lines in a single band
   * proceeding to the next band (LinesFirst), or every band for every line
   * proceeding to the next line (BandsFirst).  The default is to process all
   * lines of a band first.
   *
   * @param direction The new enumerated processing direction
   */
  void ProcessByBrick::SetProcessingDirection(ProcessingDirection direction) {
    p_reverse = direction == BandsFirst ? true : false;
  }


  /**
   * Returns the direction the data will be read, either all lines in a single
   * band proceeding to the next band (LinesFirst), or every band for every line
   * proceeding to the next line (BandsFirst).  The default is to process all
   * lines of a band first.
   *
   * @return The selected enumerated processing direction
   */
  ProcessByBrick::ProcessingDirection ProcessByBrick::GetProcessingDirection() {
    return p_reverse ? BandsFirst : LinesFirst;
  }


  /**
   * This wrapping option only applys when there are two or more input cubes.
   * If wrapping is enabled and the second cube is smaller than the first
   * The brick will be wrapped back to the beginning of the second cube
   * once brick movement reaches the end of the cube.
   * For example, if the brick shape was a single line and the second cube
   * only had one line then function passed into StartProcess will
   * receive the same contents in the 2nd input buffer every time.
   *
   * @param wrap Specifies whether or not to wrap
   */
  void ProcessByBrick::SetWrap(bool wrap) {
    p_wrapOption = wrap;
  }


  /**
   * Returns true if the wrapping option is enabled.
   * @see SetWrap()
   * @return The value of the wrapping option
   */
  bool ProcessByBrick::Wraps() {
    return p_wrapOption;
  }


  /**
   * Starts the systematic processing of the input cube by moving an arbitrary
   * shaped brick through the cube. This method requires that exactly one input
   * cube be loaded. No output cubes are produced.
   *
   * @deprecated Please use ProcessCubeInPlace, ProcessCube, or ProcessCubes 
   * @param funct (Buffer &in) Receive an nxm brick in the input buffer.
   *                                If n=1 and m=lines this will process by
   *                                columns. Likewise if n=samples and m=1 this
   *                                will process by lines.
   *
   * @throws iException::Programmer
   */
  void ProcessByBrick::StartProcess(void funct(Buffer &in)) {
    Cube *cube = NULL;
    Brick *brick = NULL;

    bool haveInput = PrepProcessCubeInPlace(&cube, &brick);

    // Loop and let the app programmer work with the bricks
    p_progress->SetMaximumSteps(brick->Bricks());
    p_progress->CheckStatus();

    for (brick->begin(); !brick->end(); (*brick)++) {
      if (haveInput) 
        cube->read(*brick);  // input only
   
      funct(*brick);

      // output only or input/output
      if ((!haveInput) || (cube->isReadWrite())) {
        cube->write(*brick);
      }

      p_progress->CheckStatus();
    }

    delete brick;
  }


  /**
   * Starts the systematic processing of the input cube by moving an arbitrary
   * shaped brick through the cube. This method requires that exactly one input
   * cube and one output cube be loaded using the SetInputCube and SetOutputCube
   * methods.
   *
   * @deprecated Please use ProcessCubeInPlace, ProcessCube, or ProcessCubes 
   * @param funct (Buffer &in, Buffer &out) Receive an nxm brick in
   *              the input buffer and output the an nxm brick. If n=1 and
   *              m=lines this will process by columns. Likewise if n=samples
   *              and m=1 this will process by lines.
   *
   * @throws iException::Programmer
   */
  void ProcessByBrick::StartProcess(void funct(Buffer &in, Buffer &out)) {
    Brick *ibrick = NULL;
    Brick *obrick = NULL;

    int numBricks = PrepProcessCube(&ibrick, &obrick);

    // Loop and let the app programmer work with the bricks
    p_progress->SetMaximumSteps(numBricks);
    p_progress->CheckStatus();

    ibrick->begin();
    obrick->begin();

    for (int i = 0; i < numBricks; i++) {
      InputCubes[0]->read(*ibrick);
      funct(*ibrick, *obrick);
      OutputCubes[0]->write(*obrick);
      p_progress->CheckStatus();
      (*ibrick)++;
      (*obrick)++;
    }

    delete ibrick;
    delete obrick;
  }


  /**
   * Starts the systematic processing of the input cube by moving an arbitrary
   * shaped brick through the cube. This method allows multiple input and output
   * cubes.
   *
   * @deprecated Please use ProcessCubeInPlace, ProcessCube, or ProcessCubes 
   * @param funct (vector<Buffer *> &in, vector<Buffer *> &out)
   *              Receive an nxm brick in the input buffer. If n=1 and m=lines
   *              this will process by columns.  Likewise if n=samples and m=1
   *              this will process by lines.
   *
   * @throws iException::Programmer
   */
  void ProcessByBrick::StartProcess(void funct(std::vector<Buffer *> &in,
                                               std::vector<Buffer *> &out)) {
    // Construct two vectors of brick buffer managers
    // The input buffer managers
    vector<Brick *> imgrs;
    vector<Buffer *> ibufs;

    // And the output buffer managers
    vector<Brick *> omgrs;
    vector<Buffer *> obufs;

    int numBricks = PrepProcessCubes(ibufs, obufs, imgrs, omgrs);

    // Loop and let the app programmer process the bricks
    p_progress->SetMaximumSteps(numBricks);
    p_progress->CheckStatus();

    for(int t = 0; t < numBricks; t++) {
      // Read the input buffers
      for(unsigned int i = 0; i < InputCubes.size(); i++) {
        InputCubes[i]->read(*ibufs[i]);
      }

      // Pass them to the application function
      funct(ibufs, obufs);

      // And copy them into the output cubes
      for(unsigned int i = 0; i < OutputCubes.size(); i++) {
        OutputCubes[i]->write(*obufs[i]);
        omgrs[i]->next();
      }

      for(unsigned int i = 0; i < InputCubes.size(); i++) {
        imgrs[i]->next();

        // if the manager has reached the end and the
        // wrap option is on, wrap around to the beginning
        if(Wraps() && imgrs[i]->end())
          imgrs[i]->begin();

        // Enforce same band
        if(imgrs[i]->Band() != imgrs[0]->Band() &&
           InputCubes[i]->bandCount() != 1) {
          imgrs[i]->SetBaseBand(imgrs[0]->Band());
        }
      }
      p_progress->CheckStatus();
    }

    for(unsigned int i = 0; i < ibufs.size(); i++) {
      delete ibufs[i];
    }
    ibufs.clear();
    imgrs.clear();

    for(unsigned int i = 0; i < obufs.size(); i++) {
      delete obufs[i];
    }
    obufs.clear();
    omgrs.clear();
  }

  /**
   * End the processing sequence and cleans up by closing cubes, freeing memory,
   *   etc.
   *
   * @deprecated Please use Finalize()
   */
  void ProcessByBrick::EndProcess() {
    p_inputBrickSizeSet = false;
    p_outputBrickSizeSet = false;
    Process::EndProcess();
  }


  /**
   * Cleans up by closing cubes and freeing memory.
   */
  void ProcessByBrick::Finalize() {
    EndProcess();
  }


  /**
   * This method blocks until the future reports that it is finished. This
   *   monitors the progress of the future and translates it's progress values
   *   into Isis progress class calls.
   *
   * @param future The future to monitor
   */
  void ProcessByBrick::BlockingReportProgress(QFuture<void> &future) {
    int isisReportedProgress = 0;
    int lastProgressValue = future.progressValue();
    // Using a mutex with a timeout isn't as bad of a hack as inheriting QThread
    //   but there ought to be a better way.
    // Does having a local mutex make sense?
    QMutex sleeper;
    sleeper.lock();
    while (!future.isFinished()) {
      sleeper.tryLock(100);

      if (future.progressValue() != lastProgressValue) {
        lastProgressValue = future.progressValue();
        // Progress min/max are reporting as 0's currently, so we're
        //   assuming the progress value is an Isis progress value.
        int isisProgressValue = lastProgressValue;
        while (isisReportedProgress < isisProgressValue) {
          p_progress->CheckStatus();
          isisReportedProgress++;
        }
      }
    }

    while (isisReportedProgress < future.progressValue()) {
      p_progress->CheckStatus();
      isisReportedProgress++;
    }

    // Need to unlock the mutex before it goes out of scope, otherwise Qt5 issues a warning
    sleeper.unlock();
  }


  /**
   * Calculates the maximum dimensions of all the cubes and returns them in a
   * vector where position 0 is the max sample, position 1 is the max line, and
   * position 2 is the max band. For example, if two cubes were passed in and
   * the first cube had 1 sample, 1 line, and 1 band, and the second cube had 2
   * samples, 2 lines, and 2 bands, the max dimensions would be 2 samples, 2
   * lines, and 2 bands.
   *
   * @param cubes The vector of cubes to calculate the maximum dimensions for.
   * @return A vector of size 3 containing the max sample dimension at position
   *   0, the max line dimension at position 1, and the max band dimension at
   *   position 2.
   */
  vector<int> ProcessByBrick::CalculateMaxDimensions(
      vector<Cube *> cubes) const {
    int maxSamples = 0;
    int maxLines = 0;
    int maxBands = 0;

    for (unsigned int i = 0; i < cubes.size(); i++) {
      int sampleCount = cubes[i]->sampleCount();
      int lineCount = cubes[i]->lineCount();
      int bandCount = cubes[i]->bandCount();

      if (sampleCount > maxSamples)
        maxSamples = sampleCount;
      if (lineCount > maxLines)
        maxLines = lineCount;
      if (bandCount > maxBands)
        maxBands = bandCount;
    }

    vector<int> maxDimensions;
    maxDimensions.push_back(maxSamples);
    maxDimensions.push_back(maxLines);
    maxDimensions.push_back(maxBands);
    return maxDimensions;
  }


  /**
   * Prepare and check to run "function" parameter for
   * StartProcess(void funct(Buffer &in)) and
   * StartProcessInPlace(Functor funct)
   *
   * @param cube - Pointer to input or output cube depending if the input
   *               cube is available
   * @param bricks - Pointer to first cube brick to be processed
   *
   * @return bool - If input cube is available(true/false)
   *
   * @history 2011-04-22 Sharmila Prasad - Ported from
   *                                StartProcess (void funct(Buffer
   *                                &in))
   */
  bool ProcessByBrick::PrepProcessCubeInPlace(Cube **cube, Brick **bricks) {
    // Error checks
    if ((InputCubes.size() + OutputCubes.size()) != 1) {
      string m = "You can only specify exactly one input or output cube";
      throw IException(IException::Programmer, m, _FILEINFO_);
    }

    bool haveInput;
    if (InputCubes.size() == 1) {

      SetBricks(InPlace);
      //  Make sure the brick size has been set
      if (!p_inputBrickSizeSet) {
        string m = "Use the SetBrickSize() or SetInputBrickSize() method to set"
                   " the input brick size";
        throw IException(IException::Programmer, m, _FILEINFO_);
      }
      // And the size is stored
      else if (p_inputBrickSamples.size() == 1) {
        SetInputBrickSize(p_inputBrickSamples[0], p_inputBrickLines[0],
                          p_inputBrickBands[0], 1);
      }

      haveInput = true;
      *cube = InputCubes[0];
      *bricks = new Brick(**cube, p_inputBrickSamples[1],
          p_inputBrickLines[1], p_inputBrickBands[1], p_reverse);
    }
    else {
      SetBricks(InPlace);
      //  Make sure the brick size has been set
      if (!p_outputBrickSizeSet) {
        string m = "Use the SetBrickSize() or SetOutputBrickSize() method to "
                   "set the output brick size";
        throw IException(IException::Programmer, m, _FILEINFO_);
      }
      // And the size is stored
      else if (p_outputBrickSamples.size() == 1) {
        SetOutputBrickSize(p_outputBrickSamples[0], p_outputBrickLines[0],
                           p_outputBrickBands[0], 1);
      }

      haveInput = false;
      *cube = OutputCubes[0];
      *bricks = new Brick(**cube, p_outputBrickSamples[1],
          p_outputBrickLines[1], p_outputBrickBands[1], p_reverse);
    }
    
    return haveInput;
  }


  /**
   * Prepare and check to run "function" parameter for
   * StartProcess(void funct(Buffer &in, Buffer &out)) and
   * StartProcessIO(Functor funct)
   *
   * @param ibrick - Pointer to first input cube brick
   * @param obrick - Pointer to first output cube brick
   *
   * @return int
   *
   * @internal
   *   @history 2011-04-22 Sharmila Prasad - Ported from
   *                           StartProcess (void funct(Buffer
   *                           &in, Buffer &out))
   */
  int ProcessByBrick::PrepProcessCube(Brick **ibrick, Brick **obrick) {
    // Error checks ... there must be one input and output
    if (InputCubes.size() != 1) {
      string m = "You must specify exactly one input cube";
      throw IException(IException::Programmer, m, _FILEINFO_);
    }
    else if (OutputCubes.size() != 1) {
      string m = "You must specify exactly one output cube";
      throw IException(IException::Programmer, m, _FILEINFO_);
    }
    SetBricks(InputOutput);
    //  Make sure the brick size has been set
    if (!p_inputBrickSizeSet || !p_outputBrickSizeSet) {
      string m = "Use the SetBrickSize(), SetInputBrickSize(), or "
                 "SetOutputBrickSize() method to set the brick sizes";
      throw IException(IException::Programmer, m, _FILEINFO_);
    }

    // Make all input and/or output cubes have the same size
    if (p_outputBrickSamples.size() == 1) {
      SetOutputBrickSize(p_outputBrickSamples[0], p_outputBrickLines[0],
                         p_outputBrickBands[0], 1);
    }
    if (p_inputBrickSamples.size() == 1) {
      SetInputBrickSize(p_inputBrickSamples[0], p_inputBrickLines[0],
                        p_inputBrickBands[0], 1);
    }

    // Construct brick buffers
    if (Wraps()) {
      // Use the size of each cube as the area for the bricks to traverse since
      // we will be wrapping if we hit the end of one, but not the other.
      *ibrick = new Brick(*InputCubes[0], p_inputBrickSamples[1],
          p_inputBrickLines[1], p_inputBrickBands[1], p_reverse);
      *obrick = new Brick(*OutputCubes[0], p_outputBrickSamples[1],
          p_outputBrickLines[1], p_outputBrickBands[1], p_reverse);
    }
    else {
      // Since we are not wrapping, we need to find the maximum size of the
      // input cube and the output cube. We will use this size when
      // constructing each of the bricks' area to traverse so that we don't
      // read into nonexistent bands of the smaller of the two cubes.
      vector<Cube *> allCubes;
      allCubes.push_back(InputCubes[0]);
      allCubes.push_back(OutputCubes[0]);
      vector<int> maxDimensions = CalculateMaxDimensions(allCubes);
      int maxSamples = maxDimensions[0];
      int maxLines = maxDimensions[1];
      int maxBands = maxDimensions[2];

      *ibrick = new Brick(maxSamples, maxLines, maxBands,
                                p_inputBrickSamples[1],
                                p_inputBrickLines[1],
                                p_inputBrickBands[1],
                                InputCubes[0]->pixelType(),
                                p_reverse);
      *obrick = new Brick(maxSamples, maxLines, maxBands,
                                p_outputBrickSamples[1],
                                p_outputBrickLines[1],
                                p_outputBrickBands[1],
                                OutputCubes[0]->pixelType(),
                                p_reverse);
    }

    int numBricks;
    if((*ibrick)->Bricks() > (*obrick)->Bricks()) {
      numBricks = (*ibrick)->Bricks();
    }
    else {
      numBricks = (*obrick)->Bricks();
    }

    return numBricks;
  }


  /**
   * Prepare and check to run "function" parameter for
   * StartProcess(void funct(vector<Buffer *> &in,
   * vector<Buffer *> &out)), StartProcessIOList(Functor funct)
   *
   * @param ibufs - input buffer manager
   * @param obufs - output buffer manager
   * @param imgrs - input brick manager
   * @param omgrs - output brick manager
   *
   * @return int  - Number of bricks
   *
   * @history 2011-04-22 Sharmila Prasad - Ported from StartProcess
   *                         (void funct(vector<Buffer *> &in,
   *                         vector<Buffer *> &out))
   */
  int ProcessByBrick::PrepProcessCubes(vector<Buffer *> & ibufs,
                                       vector<Buffer *> & obufs,
                                       vector<Brick *> & imgrs,
                                       vector<Brick *> & omgrs) {
    // Make sure we had an image
    if(InputCubes.size() == 0 && OutputCubes.size() == 0) {
      string m = "You have not specified any input or output cubes";
      throw IException(IException::Programmer, m, _FILEINFO_);
    }

    SetBricks(InputOutputList);

    //  Make sure the brick size has been set
    if(!p_inputBrickSizeSet && InputCubes.size() > 0) {
      string m = "Use the SetBrickSize() or SetInputBrick() method to set the "
                 "input brick size(s)";
      throw IException(IException::Programmer, m, _FILEINFO_);
    }
    else if(p_inputBrickSizeSet && p_inputBrickSamples.size() == 1) {
      SetInputBrickSize(p_inputBrickSamples[0], p_inputBrickLines[0],
                        p_inputBrickBands[0], InputCubes.size());
    }
    if(!p_outputBrickSizeSet && OutputCubes.size() > 0) {
      string m = "Use the SetBrickSize() or SetOutputBrick() method to set the "
                 "output brick size(s)";
      throw IException(IException::Programmer, m, _FILEINFO_);
    }
    else if(p_outputBrickSizeSet && p_outputBrickSamples.size() == 1) {
      SetOutputBrickSize(p_outputBrickSamples[0], p_outputBrickLines[0],
                         p_outputBrickBands[0], OutputCubes.size());
    }

    // this parameter holds the number of bricks to be used in processing
    // which is the maximum number of bricks of all the cubes.
    int numBricks = 0;

    // If we are not wrapping, we need to find the maximum size of the input and
    // output cubes. We will use this size when constructing each of the bricks'
    // area to traverse so that we don't read into nonexistent bands of the
    // smaller cubes.
    int maxSamples = 0;
    int maxLines = 0;
    int maxBands = 0;
    if (!Wraps()) {
      vector<Cube *> allCubes(InputCubes);
      for (unsigned int i = 0; i < OutputCubes.size(); i++)
        allCubes.push_back(OutputCubes[i]);
      vector<int> maxDimensions = CalculateMaxDimensions(allCubes);
      maxSamples = maxDimensions[0];
      maxLines = maxDimensions[1];
      maxBands = maxDimensions[2];
    }

    for (unsigned int i = 1; i <= InputCubes.size(); i++) {
      Brick *ibrick = NULL;
      if (Wraps()) {
        // Use the size of each cube as the area for the bricks to traverse
        // since we will be wrapping if we hit the end of a cube before we are
        // done processing.
        ibrick = new Brick(*InputCubes[i-1],
                            p_inputBrickSamples[i],
                            p_inputBrickLines[i],
                            p_inputBrickBands[i],
                            p_reverse);
      }
      else {
        ibrick = new Brick(maxSamples, maxLines, maxBands,
                            p_inputBrickSamples[i],
                            p_inputBrickLines[i],
                            p_inputBrickBands[i],
                            InputCubes[i - 1]->pixelType(),
                            p_reverse);
      }
      ibrick->begin();
      ibufs.push_back(ibrick);
      imgrs.push_back(ibrick);
      if(numBricks < ibrick->Bricks()) {
        numBricks = ibrick->Bricks();
      }
    }

    for (unsigned int i = 1; i <= OutputCubes.size(); i++) {
      Brick *obrick = NULL;
      if (Wraps()) {
        obrick = new Brick(*OutputCubes[i-1],
                            p_outputBrickSamples[i],
                            p_outputBrickLines[i],
                            p_outputBrickBands[i],
                            p_reverse);
      }
      else {
        obrick = new Brick(maxSamples, maxLines, maxBands,
                            p_outputBrickSamples[i],
                            p_outputBrickLines[i],
                            p_outputBrickBands[i],
                            OutputCubes[i - 1]->pixelType(),
                            p_reverse);
      }
      obrick->begin();
      obufs.push_back(obrick);
      omgrs.push_back(obrick);
      if(numBricks < obrick->Bricks()) {
        numBricks = obrick->Bricks();
      }
    }

    return numBricks;
  }


  /**
   * Initialize a process iterator given a position.
   *
   * @param position The process iterator position to initialize with.
   */
  ProcessByBrick::ProcessIterator::ProcessIterator(int position) :
      m_currentPosition(position) {
  }


  /**
   * This class fully supports copy construction.
   *
   * @param other The process iterator to copy
   */
  ProcessByBrick::ProcessIterator::ProcessIterator(
      const ProcessIterator &other) :
        m_currentPosition(other.m_currentPosition) {
  }


  /**
   * Destructor
   */
  ProcessByBrick::ProcessIterator::~ProcessIterator() {
    m_currentPosition = -1;
  }


  /**
   * Increment the process iterator to the next position.
   * @return *this
   */
  ProcessByBrick::ProcessIterator &
      ProcessByBrick::ProcessIterator::operator++() {
    m_currentPosition++;
    return *this;
  }
} // end namespace isis

