/* 
   Copyright (C) 2020 German A. Arias

   This file is part of FísicaLab application

   FísicaLab 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 3 of the License, or (at your option) any later version.
 
   This application 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
   Library General Public License for more details.
 
   You should have received a copy of the GNU General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
*/

#import <math.h>
#import "FLProcessMathematics.h"

/* These group of methods allow some mathematical operations at entries
   of table view. */

@implementation FLProcessMathematics

- (id) init
{
  // Build character sets needed for mathematical operations.
  numbers = [NSCharacterSet characterSetWithCharactersInString:
			      @".-+0123456789E"];
  [numbers retain];
  plus_minusSet =
    [NSCharacterSet characterSetWithCharactersInString: @"-+"];
  [plus_minusSet retain];
  multiplication_divisionSet =
    [NSCharacterSet characterSetWithCharactersInString: @"*/"];
  [multiplication_divisionSet retain];
  
  return self;
}

- (void) dealloc
{
  [numbers release];
  [plus_minusSet release];
  [multiplication_divisionSet release];
  [super dealloc];
}

// Process an entry in the table view.
- (NSString *) processEntry: (NSString *)anEntry
{
  NSString *result;
  NSArray *data;

  /* Check if the entry is a polynomial, a term or a variable name. And process
     the entry according at its class. */
  data = [anEntry componentsSeparatedByCharactersInSet: plus_minusSet];
  
  if ([data count] > 1)
    {
      result = [self operatePolynomial: anEntry];
    }
  else
    {
      data = [anEntry componentsSeparatedByCharactersInSet:
			multiplication_divisionSet];
      
      if ([data count] > 1)
	{
	  result = [self operateTerm: anEntry];
	}
      else if ([anEntry hasSuffix: @")"])
	{
	  result = [self operateFunction: anEntry];
	}
      else
	{
	  return anEntry;
	}
    }

  // If result is numeric, set the corresponding format.
  if ([self isNumeric: result])
    {
      result = [NSString stringWithFormat: @"%.3f", [result doubleValue]];
    }

  return result;
}

// Process a inverse trigonometric function.
- (NSString *) processAngleEntry: (NSString *)anEntry
{
  int j = -1;
  NSArray *array;
  NSString *data = [NSString stringWithString: anEntry];

  if ([data hasPrefix: @"acos("] &&
      [data hasSuffix: @")"])
    {
      data = [data stringByDeletingPrefix: @"acos("];
      data = [data stringByDeletingSuffix: @")"];

      j = 0;
    }
  else if ([data hasPrefix: @"asin("] &&
	   [data hasSuffix: @")"])
    {
      data = [data stringByDeletingPrefix: @"asin("];
      data = [data stringByDeletingSuffix: @")"];

      j = 1;
    }
  else
    {
      j = 2;
    }

  array = [data componentsSeparatedByString: @"/"];
      
  if ( ([array count] == 2) &&
       [self isNumericArray: array] &&
       ([[array objectAtIndex: 1] doubleValue] != 0) )
    {
      double angle;

      switch (j)
	{
	case 0:
	  {
	    angle = 180*acos([[array objectAtIndex: 0] doubleValue]/
			     [[array objectAtIndex: 1] doubleValue])/M_PI;
	  }
	  break;
	case 1:
	  {
	    angle = 180*asin([[array objectAtIndex: 0] doubleValue]/
			     [[array objectAtIndex: 1] doubleValue])/M_PI;
	  }
	  break;
	case 2:
	  {
	    angle = 180*atan2([[array objectAtIndex: 0] doubleValue],
			      [[array objectAtIndex: 1] doubleValue])/M_PI;
	  }
	  break;
	}
  
      return [NSString stringWithFormat: @"%.3f", angle];
    }
  else
    {
      return anEntry;
    }
}

// Process a polynomial.
- (NSString *) operatePolynomial: (NSString *)anEntry
{
  int x;
  double sum = 0;
  BOOL numeric = YES;
  NSString *result;
  NSMutableString *data = [NSMutableString stringWithString: anEntry];
  NSMutableString *operations = [NSMutableString string];
  NSMutableArray *terms = [NSMutableArray arrayWithArray:
                             [anEntry componentsSeparatedByCharactersInSet:
                                         plus_minusSet]];
  NSArray *factors;

  /*
   * Check that the final factor is not an empty string.
   * If is, this mean that the final character is an
   * operator. And this is not allowed.
   */
  if ([[terms lastObject] length] == 0)
    {
      numeric = NO;
    }

  // Get a string with all the operators in polynomial.
  if (numeric)
    {
      for (x = 0; x < [terms count]; x++)
	{
	  if ([data hasPrefix: @"+"] || [data hasPrefix: @"-"])
	    {
	      [operations appendString: [data substringToIndex: 1]];
	      [data deleteCharactersInRange: NSMakeRange (0,1)];
	    }

	  if ([data hasPrefix: [terms objectAtIndex: x]])
	    {
	      [data deletePrefix: [terms objectAtIndex: x]];
	    }
	}
    }

  // Make the operations in all terms.
  if (numeric)
    {
      if ([[terms objectAtIndex: 0] length] == 0)
	{
	  [terms replaceObjectAtIndex: 0 withObject: @"0"];
	}

      for (x = 0; x < [terms count]; x++)
	{
	  factors = [[terms objectAtIndex: x]
		      componentsSeparatedByCharactersInSet:
			multiplication_divisionSet];

	  if ([factors count] > 1)
	    {
	      result = [self operateTerm: [terms objectAtIndex: x]];

	      if ([self isNumeric: result])
		{
		  [terms replaceObjectAtIndex: x
				   withObject: result];
		}
	      else
		{
		  numeric = NO;
		  break;
		}
	    }
	  else if ([[terms objectAtIndex: x] hasSuffix: @")"])
	    {
	      result = [self operateFunction: [terms objectAtIndex: x]];

	      if ([self isNumeric: result])
		{
		  [terms replaceObjectAtIndex: x
				   withObject: result];
		}
	      else
		{
		  numeric = NO;
		  break;
		}
	    }
	  else if (![self isNumeric: [terms objectAtIndex: x]])
	    {
	      numeric = NO;
	      break;
	    }
	}
    }

  // Sum the terms.
  if (numeric)
    {
      if ([terms count] > [operations length])
	{
	  sum += [[terms objectAtIndex: 0] doubleValue];
	  [terms removeObjectAtIndex: 0];
	}

      for (x = 0; x < [terms count]; x++)
	{
	  if ([operations hasPrefix: @"+"])
	    {
	      [operations deletePrefix: @"+"];

	      sum += [[terms objectAtIndex: x] doubleValue];
	    }
	  else if ([operations hasPrefix: @"-"])
	    {
	      [operations deletePrefix: @"-"];

	      sum -= [[terms objectAtIndex: x] doubleValue];
	    }
	}
    }

  // Return the result if is numeric. Otherwise
  // return the original string.
  if (numeric)
    {
      return [NSString stringWithFormat: @"%f", sum];
    }
  else
    {
      return anEntry;
    }
}

// Process a mathematical term.
- (NSString *) operateTerm: (NSString *)anEntry
{
  int x;
  double result = 0;
  BOOL numeric = YES;
  NSString *factor;
  NSMutableString *data = [NSMutableString stringWithString: anEntry];
  NSMutableString *operations = [NSMutableString string];
  NSMutableArray *factors = [NSMutableArray arrayWithArray:
			      [anEntry componentsSeparatedByCharactersInSet:
			                  multiplication_divisionSet]];

  /*
   * Check that the first and final factors are not an empty string.
   * If one of these is empty, this mean that the first/final character
   * is an operator. And this is not allowed.
   */
  if ( ([[factors objectAtIndex: 0] length] == 0) ||
       ([[factors lastObject] length] == 0) )
    {
      numeric = NO;
    }

  // Get a string with all the operators in term.
  if (numeric)
    {
      for (x = 0; x < [factors count]; x++)
	{
	  if ([data hasPrefix: @"*"] || [data hasPrefix: @"/"])
	    {
	      [operations appendString: [data substringToIndex: 1]];
	      [data deleteCharactersInRange: NSMakeRange (0,1)];
	    }

	  if ([data hasPrefix: [factors objectAtIndex: x]])
	    {
	      [data deletePrefix: [factors objectAtIndex: x]];
	    }
	}
    }

  // Operate the funtions and check that factors/results are numerics.
  if (numeric)
    {
      for (x = 0; x < [factors count]; x++)
	{
	  if ([[factors objectAtIndex: x] hasSuffix: @")"])
	    {
	      factor = [self operateFunction: [factors objectAtIndex: x]];

	      if ([self isNumeric: factor])
		{
		  [factors replaceObjectAtIndex: x
				     withObject: factor];
		}
	      else
		{
		  numeric = NO;
		  break;
		}
	    }
	  else if (![self isNumeric: [factors objectAtIndex: x]])
	    {
	      numeric = NO;
	      break;
	    }
	}
    }

  // Make the operations.
  if (numeric)
    {
      result = [[factors objectAtIndex: 0] doubleValue];

      for (x = 1; x < [factors count]; x++)
	{
	  if ([operations hasPrefix: @"*"])
	    {
	      [operations deletePrefix: @"*"];

	      result *= [[factors objectAtIndex: x] doubleValue];
	    }
	  else if ([operations hasPrefix: @"/"])
	    {
	      [operations deletePrefix: @"/"];

	      result /= [[factors objectAtIndex: x] doubleValue];
	    }
	}
    }

  // Return the result if is numeric. Otherwise
  // return the original string.
  if (numeric)
    {
      return [NSString stringWithFormat: @"%f", result];
    }
  else
    {
      return anEntry;
    }
}

// Process a function.
- (NSString *) operateFunction: (NSString *)anEntry
{
  int j = -1;
  double number = 0;
  BOOL numeric = YES;
  NSMutableString *data = [NSMutableString stringWithString: anEntry];

  // First check the kind of funtion
  if ([data hasPrefix: @"cos("] &&
      [data hasSuffix: @")"])
    {
      [data deletePrefix: @"cos("];
      [data deleteSuffix: @")"];
      
      j = 0;
    }
  else if ([data hasPrefix: @"sin("] &&
	   [data hasSuffix: @")"])
    {
      [data deletePrefix: @"sin("];
      [data deleteSuffix: @")"];
      
      j = 1;
    }
  else if ([data hasPrefix: @"tan("] &&
	   [data hasSuffix: @")"])
    {
      [data deletePrefix: @"tan("];
      [data deleteSuffix: @")"];
      
      j = 2;
    }
  else if ([data hasPrefix: @"sqrt("] &&
	   [data hasSuffix: @")"])
    {
      [data deletePrefix: @"sqrt("];
      [data deleteSuffix: @")"];

      j = 3;
    }
  else if ([data hasPrefix: @"hypot("] &&
	   [data hasSuffix: @")"])
    {
      [data deletePrefix: @"hypot("];
      [data deleteSuffix: @")"];

      j = 4;
    }
  else if ([data hasPrefix: @"leg("] &&
	   [data hasSuffix: @")"])
    {
      [data deletePrefix: @"leg("];
      [data deleteSuffix: @")"];

      j = 5;
    }
  else if ([data hasPrefix: @"rd("] &&
	   [data hasSuffix: @")"])
    {
      [data deletePrefix: @"rd("];
      [data deleteSuffix: @")"];

      j = 6;
    }

  // Operate if there are numeric arguments
  switch (j)
    {
    case 0 ... 3:
      {
	if ([self isNumeric: data])
	  {
	    if (j == 0)
	      {
		// cosine
		number = cos(M_PI*[data doubleValue]/180);
	      }
	    else if (j == 1)
	      {
		// sine
		number = sin(M_PI*[data doubleValue]/180);
	      }
	    else if (j == 2)
	      {
		// tangent
		number = tan(M_PI*[data doubleValue]/180);
	      }
	    else
	      {
		// square root
		number = sqrt([data doubleValue]);
	      }
	  }
	else
	  {
	    numeric = NO;
	  }
      }
      break;
    case 4:
    case 5:
      {
	NSArray *factors;
	factors = [data componentsSeparatedByString: @","];

	if ([self isNumericArray: factors] &&
	    ([factors count] == 2) )
	  {
	    if (j == 4)
	      {
		// hypotenuse
		number = sqrt([[factors objectAtIndex: 0] doubleValue]*
			      [[factors objectAtIndex: 0] doubleValue] +
			      [[factors objectAtIndex: 1] doubleValue]*
			      [[factors objectAtIndex: 1] doubleValue]);
	      }
	    else
	      {
		// leg
		number = sqrt([[factors objectAtIndex: 0] doubleValue]*
			      [[factors objectAtIndex: 0] doubleValue] -
			      [[factors objectAtIndex: 1] doubleValue]*
			      [[factors objectAtIndex: 1] doubleValue]);
	      }
	  }
	else
	  {
	    numeric = NO;
	  }
      }
      break;
    case 6:
      {
	NSArray *factors;
	factors = [data componentsSeparatedByString: @","];

	if ([self isNumericArray: factors] &&
	    ([factors count] == 3) )
	  {
	    // radius
	    number = [[factors objectAtIndex: 1] doubleValue]*
	      [[factors objectAtIndex: 2] doubleValue]/
	      ( [[factors objectAtIndex: 0] doubleValue] +
		[[factors objectAtIndex: 1] doubleValue] );
	  }
	else
	  {
	    numeric = NO;
	  }
      }
      break;
    }
  
  // Return the result if there are numeric arguments. Otherwise
  // return the original string.
  if (numeric)
    {
      return [NSString stringWithFormat: @"%f", number];
    }
  else
    {
      return anEntry;
    }
}

// Check if all entries at the array are numerics or not.
- (BOOL) isNumericArray: (NSArray *)anArray
{
  BOOL numeric = YES;
  NSString *object;
  NSEnumerator *enumerator = [anArray objectEnumerator];

  while ((object = [enumerator nextObject]))
    { 
      if (![self isNumeric: object])
	{
	  numeric = NO;
	  break;
	}
    }

  return numeric;
}

// Check if a string is entirely numeric or not.
- (BOOL) isNumeric: (NSString *)anEntry
{
  BOOL numeric = YES;
  NSCharacterSet *set;

  set = [NSCharacterSet characterSetWithCharactersInString: anEntry];
  
  if (![numbers isSupersetOfSet: set] || ([anEntry length] == 0) )
    {
      numeric = NO;
    }

  return numeric;
}
@end
