//
//  Lynkeos
//  $Id:$
//
//  Created by Jean-Etienne LAMIAUD on Fri Nov 14 2008.
//  Copyright (c) 2008-2025, Jean-Etienne LAMIAUD.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
// 

#include <LynkeosCore/LynkeosObjectCache.h>
#include "ProcessStackManager.h"

NSString * const K_PROCESS_STACK_REF = @"processStackRef";

@implementation ProcessStackManager

- (id) init
{
   if ( (self = [super init]) != nil )
   {
      _item = nil;
      _intermediateRank = NSNotFound;
      _currentRank = NSNotFound;
   }

   return( self );
}

- (void) dealloc
{
   if ( _item != nil )
      [_item release];

   [super dealloc];
}

- (LynkeosImageProcessingParameter*)
                   getParameterForItem:(LynkeosProcessableImage*)item
                              andParam:(LynkeosImageProcessingParameter*)inParam
{
   LynkeosImageProcessingParameter *outParam = inParam;
   NSUInteger idx, previousIdx, stackSize, newIntermediate;

   NSAssert( item != nil, @"getParameterForItem called with nil item" );

   // Find the parameter in the stack
   if ( item != _item )
   {
      if ( _item != nil )
         [_item release];
      _item = [item retain];
      _stack = (NSMutableArray*)
                         [item getProcessingParameterWithRef:K_PROCESS_STACK_REF
                                               forProcessing:nil goUp:NO];
   }

   if ( _stack == nil && inParam != nil )
   {
      // Create a brand new one
      _stack = [NSMutableArray array];
      [item setProcessingParameter:(id <LynkeosProcessingParameter>)_stack
                           withRef:K_PROCESS_STACK_REF forProcessing:nil];
   }

   if ( inParam == nil && (_stack == nil || [_stack count] == 0) )
      // Nothing to do
      return( nil );

   if ( inParam != nil )
      idx = [_stack indexOfObject:inParam];
   else
      idx = 0;

   if ( idx == NSNotFound )
   {
      // This is the new topmost item in the stack
      [inParam setExcluded:NO];
      [_stack addObject:inParam];
      idx = [_stack count]-1;
      if ( idx > 0 )
         _intermediateRank = idx-1;
      else
         _intermediateRank = NSNotFound;
      _currentRank = idx;
   }
   else
   {
      LynkeosObjectCache *cache = [LynkeosObjectCache imageProcessingCache];

      if ( inParam != nil )
         [_stack replaceObjectAtIndex:idx withObject:inParam];
      stackSize = [_stack count];

      // Find the previous result index (by skipping excluded processes)
      newIntermediate = NSNotFound;
      for( previousIdx = (idx > 0 ? idx-1 : NSNotFound);
           previousIdx != NSNotFound;
           previousIdx = (previousIdx > 0 ? previousIdx-1 : NSNotFound) )
      {
         LynkeosImageProcessingParameter*p = [_stack objectAtIndex:previousIdx];

         if ( ![p isExcluded] )
         {
            LynkeosImageBuffer *image = nil;

            // Save the new intermediate rank for future processings
            if ( newIntermediate == NSNotFound )
               newIntermediate = previousIdx;

            // Try to find a match in the cache
            if ( cache != nil )
            {
               image = (LynkeosImageBuffer*)[cache getObjectForKey:p];
               if ( image != nil )
               {
                  // Set the cached result as the starting image
                  [_item setResult:[[image copy] autorelease]];
                  break;
               }
            }
         }
      }
      if ( _intermediateRank != newIntermediate )
         _intermediateRank = newIntermediate;

      if ( previousIdx == NSNotFound )
      {
         // Restart from the begining to get the new intermediate result
         if ( ![item isOriginal] )
            [item revertToOriginal];
         _currentRank = 0;
      }
      else
         // Restart after this process as a cached or intermediate result matches
         _currentRank = previousIdx+1;

      // Skip excluded processes to start at a real one
      for ( ; _currentRank < stackSize; _currentRank++ )
      {
         outParam = [_stack objectAtIndex:_currentRank];
         if ( ![outParam isExcluded] )
            break;
      }

      if ( _currentRank >= stackSize )
      {
         outParam = nil;   // All remaining processes are excluded
         _currentRank = NSNotFound;
      }
   }

   return( outParam );
}

- (LynkeosImageProcessingParameter*) nextParameterToProcess:
                                                  (LynkeosProcessableImage*)item
{
   NSUInteger stackSize;
   LynkeosImageProcessingParameter *outParam = nil;

   NSAssert( item == _item, @"Change of item before end of stack processing" );
   NSAssert( _currentRank != NSNotFound, @"Process unknown in the stack" );

   LynkeosObjectCache *cache = [LynkeosObjectCache imageProcessingCache];
   if ( cache != nil || _currentRank == _intermediateRank )
   {
      // Save all temporary results in the cache
      LynkeosImageBuffer* res = [_item getResult];
      if (res != nil)
      {
         LynkeosImageBuffer* image = [[res copy] autorelease];
         if ( cache != nil )
            [cache setObject:image forKey:[_stack objectAtIndex:_currentRank]];
      }
   }

   // Find the next process to execute
   _currentRank++;
   stackSize = [_stack count];
   for (; _currentRank < stackSize; _currentRank++ )
   {
      outParam = [_stack objectAtIndex:_currentRank];
      if ( ![outParam isExcluded] )
         break;
   }
   if ( _currentRank >= stackSize )
      // Stack ended or all remaining processes are excluded
      _currentRank = NSNotFound;

   return( outParam );
}

@end
