/*****************************************************************************/
// Copyright 2006-2019 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/

#include "dng_area_task.h"

#include "dng_abort_sniffer.h"
#include "dng_assertions.h"
#include "dng_auto_ptr.h"
#include "dng_flags.h"
#include "dng_globals.h"
#include "dng_host.h"
#include "dng_image.h"
#include "dng_pixel_buffer.h"
#include "dng_sdk_limits.h"
#include "dng_tile_iterator.h"
#include "dng_utils.h"

/*****************************************************************************/

dng_area_task::dng_area_task (const char *name)

    :   fMaxThreads   (kMaxMPThreads)

    ,   fMinTaskArea  (256 * 256)

    ,   fUnitCell     (1, 1)

    ,   fMaxTileSize  (256, 256)

    ,   fName ()

    {

    if (!name)
        {
        name = "dng_area_task";
        }

    fName.Set (name);

    }

/*****************************************************************************/

dng_area_task::~dng_area_task ()
    {

    }

/*****************************************************************************/

dng_rect dng_area_task::RepeatingTile1 () const
    {

    return dng_rect ();

    }

/*****************************************************************************/

dng_rect dng_area_task::RepeatingTile2 () const
    {

    return dng_rect ();

    }

/*****************************************************************************/

dng_rect dng_area_task::RepeatingTile3 () const
    {

    return dng_rect ();

    }

/*****************************************************************************/

void dng_area_task::Start (uint32 /* threadCount */,
                           const dng_rect & /* dstArea */,
                           const dng_point & /* tileSize */,
                           dng_memory_allocator * /* allocator */,
                           dng_abort_sniffer * /* sniffer */)
    {

    }

/*****************************************************************************/

void dng_area_task::Finish (uint32 /* threadCount */)
    {

    }

/*****************************************************************************/

dng_point dng_area_task::FindTileSize (const dng_rect &area) const
    {

    dng_rect repeatingTile1 = RepeatingTile1 ();
    dng_rect repeatingTile2 = RepeatingTile2 ();
    dng_rect repeatingTile3 = RepeatingTile3 ();

    if (repeatingTile1.IsEmpty ())
        {
        repeatingTile1 = area;
        }

    if (repeatingTile2.IsEmpty ())
        {
        repeatingTile2 = area;
        }

    if (repeatingTile3.IsEmpty ())
        {
        repeatingTile3 = area;
        }

    uint32 repeatV = Min_uint32 (Min_uint32 (repeatingTile1.H (),
                                             repeatingTile2.H ()),
                                             repeatingTile3.H ());

    uint32 repeatH = Min_uint32 (Min_uint32 (repeatingTile1.W (),
                                             repeatingTile2.W ()),
                                             repeatingTile3.W ());

    dng_point maxTileSize = MaxTileSize ();

    dng_point tileSize;

    tileSize.v = Min_int32 (repeatV, maxTileSize.v);
    tileSize.h = Min_int32 (repeatH, maxTileSize.h);

    // Make Xcode happy (div by zero).

    tileSize.v = Max_int32 (tileSize.v, 1);
    tileSize.h = Max_int32 (tileSize.h, 1);

    // What this is doing is, if the smallest repeating image tile is larger than the
    // maximum tile size, adjusting the tile size down so that the tiles are as small
    // as possible while still having the same number of tiles covering the
    // repeat area.  This makes the areas more equal in size, making MP
    // algorithms work better.

    // The image core team did not understand this code, and disabled it.
    // Really stupid idea to turn off code you don't understand!
    // I'm undoing this removal, because I think the code is correct and useful.

    uint32 countV = (repeatV + tileSize.v - 1) / tileSize.v;
    uint32 countH = (repeatH + tileSize.h - 1) / tileSize.h;

    // Make Xcode happy (div by zero).

    countV = Max_uint32 (countV, 1);
    countH = Max_uint32 (countH, 1);

    tileSize.v = (repeatV + countV - 1) / countV;
    tileSize.h = (repeatH + countH - 1) / countH;

    // Round up to unit cell size.

    dng_point unitCell = UnitCell ();

    if (unitCell.h != 1 || unitCell.v != 1)
        {
        tileSize.v = ((tileSize.v + unitCell.v - 1) / unitCell.v) * unitCell.v;
        tileSize.h = ((tileSize.h + unitCell.h - 1) / unitCell.h) * unitCell.h;
        }

    // But if that is larger than maximum tile size, round down to unit cell size.

    if (tileSize.v > maxTileSize.v)
        {
        tileSize.v = (maxTileSize.v / unitCell.v) * unitCell.v;
        }

    if (tileSize.h > maxTileSize.h)
        {
        tileSize.h = (maxTileSize.h / unitCell.h) * unitCell.h;
        }

    if (gPrintTimings)
        {
        fprintf (stdout,
                 "\nRender tile for below: %d x %d\n",
                 (int32) tileSize.h,
                 (int32) tileSize.v);
        }

    return tileSize;

    }

/*****************************************************************************/

void dng_area_task::ProcessOnThread (uint32 threadIndex,
                                     const dng_rect &area,
                                     const dng_point &tileSize,
                                     dng_abort_sniffer *sniffer,
                                     dng_area_task_progress *progress)
    {

    dng_rect repeatingTile1 = RepeatingTile1 ();
    dng_rect repeatingTile2 = RepeatingTile2 ();
    dng_rect repeatingTile3 = RepeatingTile3 ();

    if (repeatingTile1.IsEmpty ())
        {
        repeatingTile1 = area;
        }

    if (repeatingTile2.IsEmpty ())
        {
        repeatingTile2 = area;
        }

    if (repeatingTile3.IsEmpty ())
        {
        repeatingTile3 = area;
        }

    dng_rect tile1;

    // TODO_EP: Review & document case where these dynamic allocations appeared to have significant overhead
    AutoPtr<dng_base_tile_iterator> iter1
        (MakeTileIterator (threadIndex,
                           repeatingTile3,
                           area));

    while (iter1->GetOneTile (tile1))
        {

        dng_rect tile2;

        AutoPtr<dng_base_tile_iterator> iter2
            (MakeTileIterator (threadIndex,
                               repeatingTile2,
                               tile1));

        while (iter2->GetOneTile (tile2))
            {

            dng_rect tile3;

            AutoPtr<dng_base_tile_iterator> iter3
                (MakeTileIterator (threadIndex,
                                   repeatingTile1,
                                   tile2));

            while (iter3->GetOneTile (tile3))
                {

                dng_rect tile4;

                AutoPtr<dng_base_tile_iterator> iter4
                    (MakeTileIterator (threadIndex,
                                       tileSize,
                                       tile3));

                while (iter4->GetOneTile (tile4))
                    {

                    dng_abort_sniffer::SniffForAbort (sniffer);

                    Process (threadIndex, tile4, sniffer);

                    if (progress)
                        {
                        progress->FinishedTile (tile4);
                        }

                    }

                }

            }

        }

    }

/*****************************************************************************/

dng_base_tile_iterator * dng_area_task::MakeTileIterator (uint32 /* threadIndex */,
                                                          const dng_rect &tile,
                                                          const dng_rect &area) const
    {

    return new dng_tile_forward_iterator (tile, area);

    }

/*****************************************************************************/

dng_base_tile_iterator * dng_area_task::MakeTileIterator (uint32 /* threadIndex */,
                                                          const dng_point &tileSize,
                                                          const dng_rect &area) const
    {

    return new dng_tile_forward_iterator (tileSize, area);

    }

/*****************************************************************************/

void dng_area_task::Perform (dng_area_task &task,
                             const dng_rect &area,
                             dng_memory_allocator *allocator,
                             dng_abort_sniffer *sniffer,
                             dng_area_task_progress *progress)
    {

    dng_point tileSize (task.FindTileSize (area));

    task.Start (1, area, tileSize, allocator, sniffer);

    task.ProcessOnThread (0, area, tileSize, sniffer, progress);

    task.Finish (1);

    }

/*****************************************************************************/
/*****************************************************************************/
/*****************************************************************************/

dng_range_parallel_task::dng_range_parallel_task (dng_host &host,
                                                  int32 startIndex,
                                                  int32 stopIndex,
                                                  const char *name)

    :   dng_area_task ((name != NULL) ? name : "dng_range_parallel_task")

    ,   fHost       (host)
    ,   fStartIndex (startIndex)
    ,   fStopIndex  (stopIndex)
    ,   fIndices    ()

    {

    DNG_REQUIRE (stopIndex > startIndex,
                 "Invalid start/stop index values");

    fMinTaskArea = kDummySize * kDummySize;
    fUnitCell    = dng_point (kDummySize, kDummySize);
    fMaxTileSize = dng_point (kDummySize, kDummySize);

    }

/*****************************************************************************/

uint32 dng_range_parallel_task::RecommendedThreadCount () const
    {

    return fHost.PerformAreaTaskThreads ();

    }

/******************************************************************************/

int32 dng_range_parallel_task::MinIndicesPerThread () const
    {

    return 1;

    }

/******************************************************************************/

void dng_range_parallel_task::Run ()
    {

    uint32 threadCount = fHost.PerformAreaTaskThreads ();

    threadCount = Min_uint32 (threadCount,
                              RecommendedThreadCount ());

    const int32 items = fStopIndex - fStartIndex;

    // Find minimum number of items (indices) for each task.

    int32 minItemsPerTask = Max_int32 (1, MinIndicesPerThread ());

    // Find the number of tasks. Has to be at least 1.

    int32 tasks = Max_int32 (items / minItemsPerTask, 1);

    // Limit thread count to # of tasks.

    threadCount = Min_uint32 (threadCount,
                              (uint32) tasks);

    // Find the number of items to process per task. In general this will not
    // divide evenly, so compute this as a floating-point value. However, it
    // will be at least 1.0.

    real64 itemsPerThread64 = items / (real64) threadCount;

    // Allocate a vector to hold index ranges.

    fIndices.resize ((size_t) (threadCount + 1));

    // Populate the index ranges for each task.

    real64 idx64 = 0.0;

    for (uint32 i = 0; i <= threadCount; i++)
        {

        int32 idx = fStartIndex + Round_int32 (idx64);

        fIndices [i] = idx;

        idx64 += itemsPerThread64;

        }

    fHost.PerformAreaTask (*this,
                           dng_rect (0,
                                     0,
                                     kDummySize,
                                     kDummySize * threadCount));

    }

/******************************************************************************/

void dng_range_parallel_task::Prepare (uint32 /* threadCount */,
                                       dng_memory_allocator * /* allocator */,
                                       dng_abort_sniffer * /* sniffer */)
    {

    }

/******************************************************************************/

void dng_range_parallel_task::Start (uint32 threadCount,
                                     const dng_rect & /* dstArea */,
                                     const dng_point & /* tileSize */,
                                     dng_memory_allocator *allocator,
                                     dng_abort_sniffer *sniffer)
    {

    Prepare (threadCount,
             allocator,
             sniffer);

    }

/******************************************************************************/

void dng_range_parallel_task::Process (uint32 /* threadIndex */,
                                       const dng_rect &tile,
                                       dng_abort_sniffer *sniffer)
    {

    // Note: important to use the tile area (not the given threadIndex in the
    // above parameter) to retrieve the index range for this task.

    int32 index0 = tile.l / kDummySize;
    int32 index1 = index0 + 1;

    // This should never happen, just being safe.

    if (index0 < 0 || index1 >= (int32) fIndices.size ())
        {
        return;
        }

    int32 startIndex = fIndices [index0];
    int32 stopIndex  = fIndices [index1];

    uint32 threadIndex = (uint32) index0;

    // Do the actual work.

    ProcessRange (threadIndex,
                  startIndex,
                  stopIndex,
                  sniffer);

    }

/*****************************************************************************/

class dng_range_parallel_func_task: public dng_range_parallel_task
    {

    private:

        const uint32 fMinIndicesPerThread;

        const uint32 fRecommendedThreadCount;

        const dng_range_parallel_task::function_t &fFunc;

    public:

        dng_range_parallel_func_task (dng_host &host,
                                      const info &params,
                                      const char *taskName,
                                      const dng_range_parallel_task::function_t &func)

            :   dng_range_parallel_task (host,
                                         params.fBegin,
                                         params.fEnd,
                                         taskName)

            ,   fMinIndicesPerThread (Max_uint32 (params.fMinIndicesPerThread, 1))

            ,   fRecommendedThreadCount (params.fRecommendedThreadCount)

            ,   fFunc (func)

            {

            }

        virtual uint32 RecommendedThreadCount () const
            {

            if (fRecommendedThreadCount > 0)
                {
                return fRecommendedThreadCount;
                }

            return dng_range_parallel_task::RecommendedThreadCount ();

            }

        virtual int32 MinIndicesPerThread () const
            {

            return fMinIndicesPerThread;

            }

        virtual void ProcessRange (uint32 threadIndex,
                                   int32 startIndex,
                                   int32 stopIndex,
                                   dng_abort_sniffer *sniffer)
            {

            dng_range_parallel_task::range r;

            r.fThreadIndex = threadIndex;
            r.fBegin       = startIndex;
            r.fEnd         = stopIndex;
            r.fSniffer     = sniffer;

            fFunc (r);

            }

    };

/*****************************************************************************/

void dng_range_parallel_task::Do (dng_host &host,
                                  const info &params,
                                  const char *taskName,
                                  const function_t &func)
    {

    dng_range_parallel_func_task task (host,
                                       params,
                                       taskName,
                                       func);

    task.Run ();

    }

/*****************************************************************************/
/*****************************************************************************/
/*****************************************************************************/

dng_rect dng_get_buffer_task::RepeatingTile1 () const
    {

    return fSrc.RepeatingTile ();

    }

/*****************************************************************************/

void dng_get_buffer_task::Process (uint32 /* threadIndex */,
                                   const dng_rect &tile,
                                   dng_abort_sniffer * /* sniffer */)
    {

    dng_pixel_buffer temp = fDst;

    temp.fData = (void *) fDst.DirtyPixel (tile.t, tile.l, temp.fPlane);

    temp.fArea = tile;

    fSrc.Get (temp,
              fEdgeOption);

    }

/*****************************************************************************/
/*****************************************************************************/
/*****************************************************************************/

dng_copy_buffer_task::dng_copy_buffer_task (const dng_pixel_buffer &src,
                                            dng_pixel_buffer &dst)

    :   dng_area_task ("dng_copy_buffer_task")

    ,   fSrc (src)
    ,   fDst (dst)

    {

    DNG_REQUIRE (src.Planes () == dst.Planes (),
                 "Mismatched planes");

    }

/*****************************************************************************/

dng_rect dng_copy_buffer_task::RepeatingTile1 () const
    {

    return dng_rect (128, 128);

    }

/*****************************************************************************/

void dng_copy_buffer_task::Process (uint32 /* threadIndex */,
                                    const dng_rect &tile,
                                    dng_abort_sniffer * /* sniffer */)
    {

    fDst.CopyArea (fSrc,
                   tile,
                   0,
                   fDst.Planes ());

    }

/*****************************************************************************/
/*****************************************************************************/
/*****************************************************************************/

dng_rect dng_put_buffer_task::RepeatingTile1 () const
    {

    return fDst.RepeatingTile ();

    }

/*****************************************************************************/

void dng_put_buffer_task::Process (uint32 /* threadIndex */,
                                   const dng_rect &tile,
                                   dng_abort_sniffer * /* sniffer */)
    {

    dng_pixel_buffer temp = fSrc;

    temp.fData = (void *) fSrc.ConstPixel (tile.t,
                                           tile.l,
                                           temp.fPlane);

    temp.fArea = tile;

    fDst.Put (temp);

    }

/*****************************************************************************/
