<?php

if ( ! class_exists( 'GP_Plugin' ) ) {
	return;
}

class GP_Advanced_Calculations extends GP_Plugin {

	private static $instance = null;

	protected $_version     = GP_ADVANCED_CALCULATIONS_VERSION;
	protected $_path        = 'gp-advanced-calculations/gp-advanced-calculations.php';
	protected $_full_path   = __FILE__;
	protected $_slug        = 'gp-advanced-calculations';
	protected $_title       = 'Gravity Wiz Advanced Calculations';
	protected $_short_title = 'Advanced Calculations';

	/* pi() only goes to 13 decimal places by default and does not match the JS output. */
	const PI_20_DECIMAL_PLACES = 3.14159265358979323846;

	public static function get_instance() {
		if ( self::$instance === null ) {
			self::$instance = new self;
		}

		return self::$instance;
	}

	public function minimum_requirements() {
		return array(
			'gravityforms' => array(
				'version' => '1.8',
			),
			'wordpress'    => array(
				'version' => '5.5',
			),
			'plugins'      => array(
				'gravityperks/gravityperks.php' => array(
					'name'    => 'Gravity Perks',
					'version' => '2.2.5',
				),
			),
		);
	}

	public function init() {
		parent::init();

		load_plugin_textdomain( 'gp-advanced-calculations', false, basename( dirname( __file__ ) ) . '/languages/' );

		add_filter( 'gform_register_init_scripts', array( $this, 'add_init_script' ) );
		add_action( 'gform_field_advanced_settings', array( $this, 'output_formula_cheatsheet' ) );

		// Priority of 15 so plugins such as GPECF can do their replacements first
		add_filter( 'gform_calculation_formula', array( $this, 'process_calculation_formula' ), 15, 4 );
	}

	public function scripts() {
		$scripts = array(
			array(
				'handle'  => 'gp-advanced-calculations',
				'src'     => $this->get_base_url() . '/js/built/gp-advanced-calculations.js',
				'version' => $this->_version,
				'deps'    => array(),
				'enqueue' => array(
					array( $this, 'is_applicable_form' ),
				),
			),
			array(
				'handle'   => 'gp-advanced-calculations-form-editor',
				'src'      => $this->get_base_url() . '/js/built/gp-advanced-calculations-form-editor.js',
				'version'  => $this->_version,
				'callback' => array( $this, 'localize_form_editor_script' ),
				'deps'     => array( 'gform_gravityforms', 'jquery' ),
				'enqueue'  => array(
					array( 'admin_page' => array( 'form_editor' ) ),
				),
			),
		);

		return array_merge( parent::scripts(), $scripts );
	}

	public function localize_form_editor_script() {
		wp_localize_script( 'gp-advanced-calculations-form-editor', 'GPAC', array(
			'strings' => array(
				'formulaCheatsheet' => esc_html__( 'Formula Cheatsheet', 'gp-advanced-calculations' ),
				'cheatsheetTitle'   => esc_html__( 'GP Advanced Calculations Formula Cheatsheet', 'gp-advanced-calculations' ),
				'formulaError'      => esc_html__( '🚫️ There appears to be a problem with the formula.', 'gp-advanced-calculations' ),
				'formulaValid'      => esc_html__( '✅ The formula appears to be valid.', 'gp-advanced-calculations' ),
			),
		) );
	}

	function add_init_script( $form ) {

		if ( ! $this->is_applicable_form( $form ) ) {
			return;
		}

		$script = 'new GPAdvancedCalculations({ formId: ' . $form['id'] . ' });';
		$slug   = sprintf( 'gp_advanced_calculations_%d', $form['id'] );

		GFFormDisplay::add_init_script( $form['id'], $slug, GFFormDisplay::ON_PAGE_RENDER, $script );

	}

	function process_calculation_formula( $formula, $field, $form, $entry ) {

		$formula = $this->replace_shorthand_variables( $formula, $form, $field, $entry );
		$formula = $this->process_conditionals( $formula, $field, $form, $entry );
		$formula = $this->process_exponents( $formula, $field, $form, $entry );
		$formula = $this->process_functions( $formula, $field, $form, $entry );

		return $formula;
	}

	function process_conditionals( $formula, $field, $form, $entry ) {

		$pattern         = '/^if\s*\((.+)\)\s*:[\s]+(.+)[\s]+((?:elseif\s*\(.+\)\s*:[\s]+.+[\s]+)*?)else\s*:[\s]+(.+)[\s]+endif\s*;/';
		$else_if_pattern = '/(?:elseif\s*\((.+)\)\s*:[\s]+(.+))+/';

		preg_match_all( $pattern, $formula, $matches, PREG_SET_ORDER );

		foreach ( $matches as $match ) {
			$full_match = $match[0];
			$else_ifs   = $match[3];
			$statements = array();

			$statements[] = array(
				'tag'       => 'if',
				'condition' => $this->replace_calculation_variables( $match[1], $form, $field, $entry ),
				'formula'   => $match[2],
			);

			if ( $else_ifs ) {
				preg_match_all( $else_if_pattern, $else_ifs, $else_if_matches, PREG_SET_ORDER );

				foreach ( $else_if_matches as $else_if_match ) {
					$statements[] = array(
						'tag'       => 'elseif',
						'condition' => $this->replace_calculation_variables( $else_if_match[1], $form, $field, $entry ),
						'formula'   => $else_if_match[2],
					);
				}
			}

			$statements[] = array(
				'tag'       => 'else',
				'condition' => null,
				'formula'   => $match[4],
			);

			foreach ( $statements as $statement ) {
				$tag       = $statement['tag'];
				$condition = $statement['condition'];

				if ( $condition ) {
					$condition = $this->process_exponents( $condition, $field, $form, $entry );
					$condition = $this->process_functions( $condition, $field, $form, $entry );
				}

				if ( ( $tag === 'else' && $condition === null ) || ! ! $this->eval_formula( $condition ) ) {
					$formula = str_replace( $full_match, $statement['formula'], $formula );
					break;
				}
			}
		}

		return $formula;
	}

	function process_exponents( $formula, $field, $form, $entry ) {

		$items     = $this->parse_formula( $formula );
		$processed = array();

		while ( ! empty( $items ) ) {

			$root_item = array_shift( $items );

			if ( $root_item['type'] !== 'operator' || $root_item['value'] !== '^' ) {
				$processed[] = $root_item;
				continue;
			}

			$target_depth = $root_item['depth'];

			// get the base: dip back into the processed items and find all items that are at the same or greater depth than our target
			$base = array();
			while ( ! empty( $processed ) ) {
				$item = array_pop( $processed );
				// accepts a single item at the same depth and only if an item of a greater depth has not already been added
				if ( $item['depth'] === $target_depth ) {
					if ( empty( $base ) ) {
						$base[] = $item;
					} else {
						$processed[] = $item;
					}
					break;
					// accepts multiple items at a greater depth
				} elseif ( $item['depth'] > $target_depth ) {
					$base[] = $item;
				} else {
					$processed[] = $item;
					break;
				}
			}
			$base = array_reverse( $base );

			// get the power: only the first item at target depth or all items below target depth
			$pow = array();
			while ( ! empty( $items ) ) {
				$item = array_shift( $items );
				// accepts a single item at the same depth and only if an item of a greater depth has not already been added
				if ( $item['depth'] === $target_depth ) {
					if ( empty( $pow ) ) {
						$pow[] = $item;
					} else {
						array_unshift( $items, $item );
					}
					break;
					// accepts multiple items at a greater depth
				} elseif ( $item['depth'] > $target_depth ) {
					$pow[] = $item;
					// if item is at a lesser depth, we're done collecting the pow. Add the current item back to the queue
					// so the outer while loop and loop for any other exponents.
				} else {
					array_unshift( $items, $item );
					break;
				}
			}

			$base = $this->build_formula( $base );
			$base = $this->replace_calculation_variables( $base, $form, $field, $entry );
			$base = $this->process_functions( $base, $field, $form, $entry );
			$base = $this->eval_formula( $base );

			$pow = $this->build_formula( $pow );
			$pow = $this->replace_calculation_variables( $pow, $form, $field, $entry );
			$pow = $this->process_functions( $pow, $field, $form, $entry );
			$pow = $this->eval_formula( $pow );

			$value = pow( $base, $pow );

			$root_item['value'] = $value;

			$processed[] = $root_item;
		}

		$formula = $this->build_formula( $processed );

		return $formula;
	}

	public function eval_formula( $formula ) {
		if ( preg_match( '/^[0-9 +\-\/*()^><.=%&|!]+$/', $formula ) ) {
			$prev_reporting_level = error_reporting( 0 );
			try {
				// Eval is checked using regular expression prior to eval-ing.
				// phpcs:ignore Squiz.PHP.Eval.Discouraged
				$result = eval( "return {$formula};" );
			} catch ( Exception $e ) {
				GFCommon::log_debug( __METHOD__ . sprintf( '(): Formula could not be parsed: "%s".', $e->getMessage() ) );
				$result = 0;
			}
			error_reporting( $prev_reporting_level );
		} else {
			$result = 0;
		}

		return $result;
	}

	function process_functions( $formula, $field, $form, $entry ) {

		$items     = $this->parse_formula( $formula );
		$processed = array();
		$functions = array(
			'abs'     => function ( $value ) {
				return abs( $value );
			},
			'average' => function () {
				return array_sum( func_get_args() ) / count( func_get_args() );
			},
			'ceil'    => function ( $value ) {
				return ceil( $value );
			},
			'exp'     => function ( $value ) {
				return exp( $value );
			},
			'floor'   => function ( $value ) {
				return floor( $value );
			},
			'fv'      => function( $rate, $number_of_periods, $payment_amount, $present_value, $end_or_beginning = 0 ) {
				$pow = pow( 1 + $rate, $number_of_periods );

				if ( $rate ) {
					$fv = ( $payment_amount * ( 1 + $rate * $end_or_beginning ) * ( 1 - $pow ) / $rate ) - $present_value * $pow;
				} else {
					$fv = -1 * ( $present_value + $payment_amount * $number_of_periods );
				}

				return $fv;
			},
			'ln'      => function ( $value ) {
				return log( $value, M_E );
			},
			'log'     => function ( $value ) {
				return log( $value, 10 );
			},
			'max'     => function () {
				return call_user_func_array( 'max', func_get_args() );
			},
			'min'     => function () {
				return call_user_func_array( 'min', func_get_args() );
			},
			'pi'      => function () {
				return self::PI_20_DECIMAL_PLACES;
			},
			'round'   => function ( $value ) {
				// JavaScript half-rounding behaves differently from PHP's, and is not configurable.
				// As a result, we need to make PHP behave the same way that JS does.
				//
				// see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round#description
				return round( $value, 0, $value < 0 ? PHP_ROUND_HALF_DOWN : PHP_ROUND_HALF_UP );
			},
			'sqrt'    => function ( $value ) {
				return sqrt( $value );
			},
		);

		while ( ! empty( $items ) ) {

			$root_item = array_shift( $items );

			if ( $root_item['type'] !== 'function' || ! isset( $functions[ strtolower( $root_item['value'] ) ] ) ) {
				$processed[] = $root_item;
				continue;
			}

			$target_depth = $root_item['depth'];
			$contained    = array();
			$item         = null;

			while ( ! empty( $items ) ) {
				$item = array_shift( $items );
				if ( $item['depth'] > $target_depth ) {
					$contained[] = $item;
				} else {
					break;
				}
			}

			$contained = $this->replace_calculation_variables( $this->build_formula( $contained, $target_depth + 1 ), $form, $field, $entry );

			// Replace inner functions with a placeholder, so they can be processed later
			$nested_function_placeholders = array();
			$nested_function_i            = 0;

			$nested_function_regex = '/[a-z0-9]+\(([^()]+|[^(]+\([^)]*\)[^()]*)\)/i';

			preg_match_all( $nested_function_regex, $contained, $nested_function_matches, PREG_SET_ORDER );

			foreach ( $nested_function_matches as $match ) {
				$placeholder = 'NESTED_' . $nested_function_i;
				$nested_function_i++;

				$nested_function_placeholders[ $placeholder ] = $match[0];

				$contained = str_replace( $match[0], $placeholder, $contained );
			}

			$arguments = array_map(function( $item ) use ( $nested_function_placeholders, $field, $form, $entry ) {
				$item = trim( $item );

				// Replace nested function placeholders back to the actual function call
				if ( strpos( $item, 'NESTED_' ) === 0 ) {
					$item = $nested_function_placeholders[ $item ];
					$item = $this->process_functions( $item, $field, $form, $entry );
				}

				return $this->eval_formula( $item );
			}, explode( ',', $contained ));

			$root_item['value'] = call_user_func_array( $functions[ strtolower( $root_item['value'] ) ], $arguments );

			$processed[] = $root_item;

			if ( $item && $item['depth'] <= $target_depth ) {
				$processed[] = $item;
			}
		}

		$formula = $this->build_formula( $processed );

		return $formula;
	}

	/**
	 * Moves the pointer forward when parsing the formula until the end of a specified pattern.
	 *
	 * char is always returned even if there's no match.
	 *
	 * @param  $formula string The formula being parsed.
	 * @param  $char    string The current character being read. Can be thought of as the pointer.
	 * @param  $pattern string The regular expression to find the end of the match.
	 */
	function match_until( $formula, $char, $pattern ) {
		if ( preg_match( $pattern, $formula, $last_letter, PREG_OFFSET_CAPTURE ) ) {
			$last_letter = strlen( $last_letter[0][0] );
			$value       = $char . substr( $formula, 0, $last_letter );
			$formula     = substr( $formula, $last_letter );
		} else {
			$value = $char;
		}

		return array(
			$formula,
			$value,
		);
	}

	function parse_formula( $formula ) {

		$formula = trim( preg_replace( '/\s+/', ' ', $formula ) );

		$items = array(); // 'group', 'value', 'operator'
		$depth = 0;

		while ( $formula !== '' ) {

			$char    = substr( $formula, 0, 1 );
			$formula = substr( $formula, 1 );

			// substr's behavior changed in PHP 8 (they claim), but I've seen behavior be different between PHP 5.6 and 7.3
			if ( $formula === false ) {
				$formula = '';
			}

			$item = array();

			if ( $char === '(' ) {
				$depth ++;
			} elseif ( $char === ')' ) {
				$depth --;
			} elseif ( in_array( $char, array( '*', '-', '+', '/', '^', '%' ), true ) ) {
				$item = array(
					'depth' => $depth,
					'type'  => 'operator',
					'value' => $char,
				);
			} elseif ( $char === '{' ) {
				$closing_brace = strpos( $formula, '}' );
				$value         = $char . substr( $formula, 0, $closing_brace + 1 );
				$formula       = substr( $formula, $closing_brace + 1 );
				$item          = array(
					'depth' => $depth,
					'type'  => 'value',
					'value' => $value,
				);
			} elseif ( $char === ',' ) {
				$item = array(
					'depth' => $depth,
					'type'  => 'sep',
					'value' => ',',
				);
			} elseif ( is_numeric( $char ) ) {
				list( $formula, $value ) = $this->match_until( $formula, $char, '/^[0-9.]+/' ); // Includes decimals; could be stricter.

				$item = array(
					'depth' => $depth,
					'type'  => 'value',
					'value' => $value,
				);
			} elseif ( (bool) preg_match( '/[a-zA-Z]/i', $char ) ) {
				list( $formula, $value ) = $this->match_until( $formula, $char, '/^[a-zA-Z]+/' );

				$item = array(
					'depth' => $depth,
					'type'  => 'function',
					'value' => $value,
				);
			} elseif ( (bool) preg_match( '/[<>!=]/', $char ) ) {
				list( $formula, $value ) = $this->match_until( $formula, $char, '/^=+/' );

				$item = array(
					'depth' => $depth,
					'type'  => 'comparisonOp',
					'value' => ' ' . $value . ' ',
				);
			} elseif ( (bool) preg_match( '/[&|]/', $char ) ) {
				list( $formula, $value ) = $this->match_until( $formula, $char, '/^[&|]+/' );

				/* char is always returned in match_until(). We can't do anything with a single & or | so we throw it out. */
				if ( strlen( $value ) === 1 ) {
					continue;
				}

				$item = array(
					'depth' => $depth,
					'type'  => 'logicalOp',
					'value' => ' ' . $value . ' ',
				);
			}

			if ( ! empty( $item ) ) {
				$items[] = $item;
			}
		}

		return $items;
	}

	/**
	 * @param $parsed_formula
	 * @param $starting_depth
	 *
	 * @return string
	 */
	function build_formula( $parsed_formula, $starting_depth = 0 ) {

		$depth   = 0;
		$formula = '';

		foreach ( $parsed_formula as $item ) {

			/*
			 * Remove starting depth from the item. For instance, if we're taking only items from inside a
			 * function call, we give it the starting depth of the function to avoid building a formula that is wrapped
			 * in numerous parentheses.
			 */
			$item['depth'] -= $starting_depth;

			if ( $depth < $item['depth'] ) {
				$formula .= str_repeat( '(', $item['depth'] - $depth );
				$depth    = $item['depth'];
			} elseif ( $depth > $item['depth'] ) {
				$formula .= str_repeat( ')', $depth - $item['depth'] );
				$depth    = $item['depth'];
			}

			$formula .= $item['value'];

		}

		if ( $depth > 0 ) {
			$formula .= str_repeat( ')', $depth );
		}

		return $formula;
	}

	function replace_calculation_variables( $expr, $form, $field, $entry ) {
		$expr = trim( preg_replace( '/\s+/', ' ', $expr ) );
		preg_match_all( '/{[^{]*?:(\d+(\.\d+)?)(:(.*?))?}/mi', $expr, $matches, PREG_SET_ORDER );

		if ( is_array( $matches ) ) {
			foreach ( $matches as $match ) {

				list( $text, $input_id ) = $match;

				$value = GFCommon::get_calculation_value( $input_id, $form, $entry, $field->numberFormat );
				$value = apply_filters( 'gform_merge_tag_value_pre_calculation', $value, $input_id, rgar( $match, 4 ), $field, $form, $entry );
				$expr  = str_replace( $text, $value, $expr );

			}
		}

		return $this->replace_shorthand_variables( $expr, $form, $field, $entry );
	}

	function replace_shorthand_variables( $expr, $form, $field, $entry ) {
		preg_match_all( '/F(\d+(?:\.\d+)?)/m', $expr, $shorthand_matches, PREG_SET_ORDER );

		if ( is_array( $shorthand_matches ) ) {
			foreach ( $shorthand_matches as $match ) {
				list( $text, $input_id ) = $match;

				$value = GFCommon::get_calculation_value( $input_id, $form, $entry, $field->numberFormat );
				$value = apply_filters( 'gform_merge_tag_value_pre_calculation', $value, $input_id, null, $field, $form, $entry );

				/* Use negative lookahead to prevent partial matches. e.g. if replacing F4, don't replace F43. */
				$expr = preg_replace( '/' . $text . '(?!\d)/', $value, $expr );
			}
		}

		return $expr;
	}

	function is_applicable_form( $form ) {
		if ( empty( $form['fields'] ) ) {
			return false;
		}

		foreach ( $form['fields'] as $field ) {
			if ( $field->has_calculation() ) {
				return true;
			}
		}

		return false;
	}

	function output_formula_cheatsheet() {
		?>
		<div id="gpac_cheatsheet" style="display:none;">

			<style>
				#gpac-cheatsheet-content h2:first-child {
					margin-top: 0;
				}
				#gpac-cheatsheet-content p {
					padding: 0;
				}
				.gpac-operators {
					margin-bottom: calc( 1em + 10px );
				}
				th.gpac-operators-header,
				.gpac-operators tbody td:first-child {
					width: 5rem;
					text-align: center;
				}
			</style>

			<div id="gpac-cheatsheet-content">

				<h2><?php esc_html_e( 'Rules', 'gp-advanced-calculations' ); ?></h2>

				<ul class="description-list">
					<li><?php _e( 'Numbers must be entered as decimal dot format (good: <code>5.20</code>) and cannot include thousands separators (bad: <code>1,000</code>) or currency symbols (bad: <code>$10</code>).', 'gp-advanced-calculations' ); ?></li>
					<li><?php esc_html_e( 'Fields referenced in variables must be positioned before the calculation field and must not be hidden by conditional logic.', 'gp-advanced-calculations' ); ?></li>
				</ul>

				<p>
					<?php
					// translators: placeholder is a URL to Gravity Forms documentation
					printf( __( 'For a more exhaustive list of rules, see the official <a href="%s" target="_blank">Gravity Forms documentation</a>.', 'gp-advanced-calculations' ), 'https://docs.gravityforms.com/using-calculations/#:~:text=C)%20Final%20formula-,Number%20Formatting%20Rules,-This%20section%20explains' );
					?>
				</p>

				<h2><?php esc_html_e( 'Arithmetic', 'gp-advanced-calculations' ); ?></h2>

				<table class="gpac-operators widefat fixed">
					<thead>
					<tr>
						<th class="gpac-operators-header"><?php esc_html_e( 'Operator', 'gp-advanced-calculations' ); ?></th>
						<th><?php esc_html_e( 'Description', 'gp-advanced-calculations' ); ?></th>
					</tr>
					</thead>
					<tbody>
					<tr class="alternate">
						<td><strong class="code">+</strong></td>
						<td><?php esc_html_e( 'Add', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr>
						<td><strong class="code">-</strong></td>
						<td><?php esc_html_e( 'Subtract', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr class="alternate">
						<td><strong class="code">*</strong></td>
						<td><?php esc_html_e( 'Multiply', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr>
						<td><strong class="code">/</strong></td>
						<td><?php esc_html_e( 'Divide', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr class="alternate">
						<td><strong class="code">( )</strong></td>
						<td><?php esc_html_e( 'Parenthesis; enclose parts of the formula to be calculated first.', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr>
						<td><strong class="code">%</strong></td>
						<td><?php esc_html_e( 'Modulo; returns the remainder of a division.', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr class="alternate">
						<td><strong class="code">^</strong></td>
						<td><?php esc_html_e( 'Exponentiation; raise a number to the power of an exponent.', 'gp-advanced-calculations' ); ?></td>
					</tr>
					</tbody>
				</table>

				<h2><?php esc_html_e( 'Field Variables', 'gp-advanced-calculations' ); ?></h2>

				<p><?php esc_html_e( 'Use values from other fields in your calculations. Fields may be referenced in various ways but they functionally identical. Use the style that makes your formula most readable.', 'gp-advanced-calculations' ); ?></p>

				<table class="widefat fixed">
					<thead>
					<tr>
						<th><?php esc_html_e( 'Style', 'gp-advanced-calculations' ); ?></th>
						<th><?php esc_html_e( 'Examples', 'gp-advanced-calculations' ); ?></th>
					</tr>
					</thead>
					<tbody>
					<tr class="alternate">
						<td><?php esc_html_e( 'Shorthand', 'gp-advanced-calculations' ); ?></td>
						<td><strong class="code">F1</strong>, <strong class="code">F2.3</strong></td>
					</tr>
					<tr>
						<td><?php esc_html_e( 'Merge Tag', 'gp-advanced-calculations' ); ?></td>
						<td><strong class="code">{Field A:1}</strong>, <strong class="code">{Field B:2.3}</strong></td>
					</tr>
					<tr class="alternate">
						<td><?php esc_html_e( 'Abbreviated Merge Tag', 'gp-advanced-calculations' ); ?></td>
						<td><strong class="code">{:1}</strong>, <strong class="code">{:2.3}</strong></td>
					</tr>
					</tbody>
				</table>

				<p><?php _e( '<strong>Tip:</strong> Hover over shorthand and abbreviated merge tag variables in the formula editor to see the field\'s label.', 'gp-advanced-calculations' ); ?></p>

				<h2><?php esc_html_e( 'Conditional Statements', 'gp-advanced-calculations' ); ?></h2>

				<p><?php esc_html_e( 'Use conditional statements to specify different formulas to be used when different conditions are met. Conditions are evaluated from left to right, top to bottom. Multiple expressions/statements are allowed per block.', 'gp-advanced-calculations' ); ?></p>

				<h3><?php esc_html_e( 'if/else', 'gp-advanced-calculations' ); ?></h3>

				<pre class="code" style="padding: 1rem;background: #f5f5f5;border: 1px solid #d5d5d5;border-radius: 2px;">
if( F1 < 5.5 ):
	F1 * 50
else:
	0
endif;</pre>

				<h3><?php esc_html_e( 'if/elseif/else', 'gp-advanced-calculations' ); ?></h3>

				<pre class="code" style="padding: 1rem;background: #f5f5f5;border: 1px solid #d5d5d5;border-radius: 2px;">
if( F1 < 5.5 ):
	F1 * 50
elseif( F1 >= 10 ):
	F1 * 25
else:
	0
endif;</pre>

				<p><?php _e( '<strong>Tip:</strong> You may use as many elseif statements as needed.', 'gp-advanced-calculations' ); ?></p>

				<h3><?php esc_html_e( 'Conditional Operators', 'gp-advanced-calculations' ); ?></h3>

				<table class="gpac-operators widefat fixed">
					<thead>
					<tr>
						<th class="gpac-operators-header"><?php esc_html_e( 'Operator', 'gp-advanced-calculations' ); ?></th>
						<th><?php esc_html_e( 'Description', 'gp-advanced-calculations' ); ?></th>
					</tr>
					</thead>
					<tbody>
					<tr class="alternate">
						<td><strong class="code">==</strong></td>
						<td><?php esc_html_e( 'Equal to', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr>
						<td><strong class="code">!=</strong></td>
						<td><?php esc_html_e( 'Not equal to', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr class="alternate">
						<td><strong class="code">&gt;</strong></td>
						<td><?php esc_html_e( 'Greater than', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr>
						<td><strong class="code">&lt;</strong></td>
						<td><?php esc_html_e( 'Less than', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr class="alternate">
						<td><strong class="code">&gt;=</strong></td>
						<td><?php esc_html_e( 'Greater than or equal to', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr>
						<td><strong class="code">&lt;=</strong></td>
						<td><?php esc_html_e( 'Less than or equal to', 'gp-advanced-calculations' ); ?></td>
					</tr>
					</tbody>
				</table>

				<h2><?php esc_html_e( 'Functions', 'gp-advanced-calculations' ); ?></h2>

				<table class="widefat fixed">
					<thead>
					<tr>
						<th><?php esc_html_e( 'Function/Usage', 'gp-advanced-calculations' ); ?></th>
						<th><?php esc_html_e( 'Description', 'gp-advanced-calculations' ); ?></th>
					</tr>
					</thead>
					<tbody>
					<tr class="alternate">
						<td>
							<strong class="code"><?php esc_html_e( 'abs( value )', 'gp-advanced-calculations' ); ?></strong>
						</td>
						<td><?php esc_html_e( 'Returns absolute value of a number', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr>
						<td>
							<strong class="code"><?php esc_html_e( 'average( value1, [value2, ...] )', 'gp-advanced-calculations' ); ?></strong>
						</td>
						<td><?php esc_html_e( 'Returns the average value in the args', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr class="alternate">
						<td>
							<strong class="code"><?php esc_html_e( 'ceil( value )', 'gp-advanced-calculations' ); ?></strong>
						</td>
						<td><?php esc_html_e( 'Rounds number up to the next largest integer', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr>
						<td>
							<strong class="code"><?php esc_html_e( 'exp( value )', 'gp-advanced-calculations' ); ?></strong>
						</td>
						<td><?php esc_html_e( 'Returns e^x, where e is Euler\'s number and x is the provided argument', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr class="alternate">
						<td>
							<strong class="code"><?php esc_html_e( 'floor( value )', 'gp-advanced-calculations' ); ?></strong>
						</td>
						<td><?php esc_html_e( 'Returns the largest integer less than or equal to a number', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr>
						<td>
							<strong class="code"><?php esc_html_e( 'fv( rate, numberOfPeriods, paymentAmount, presentValue, [endOrBeginning] )', 'gp-advanced-calculations' ); ?></strong>
						</td>
						<td><?php esc_html_e( 'Returns the future value of an investment based on a constant interest rate', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr class="alternate">
						<td>
							<strong class="code"><?php esc_html_e( 'ln( value )', 'gp-advanced-calculations' ); ?></strong>
						</td>
						<td><?php esc_html_e( 'Returns the logarithm of a number, base e', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr>
						<td>
							<strong class="code"><?php esc_html_e( 'log( value )', 'gp-advanced-calculations' ); ?></strong>
						</td>
						<td><?php esc_html_e( 'Returns the logarithm of a number, base 10', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr class="alternate">
						<td>
							<strong class="code"><?php esc_html_e( 'max( value1, [value2, ...] )', 'gp-advanced-calculations' ); ?></strong>
						</td>
						<td><?php esc_html_e( 'Returns the largest value in the list of arguments', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr>
						<td>
							<strong class="code"><?php esc_html_e( 'min( value1, [value2, ...] )', 'gp-advanced-calculations' ); ?></strong>
						</td>
						<td><?php esc_html_e( 'Returns the smallest value in the list of arguments', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr class="alternate">
						<td>
							<strong class="code"><?php esc_html_e( 'round( value )', 'gp-advanced-calculations' ); ?></strong>
						</td>
						<td><?php esc_html_e( 'Rounds a number to the nearest integer', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr>
						<td>
							<strong class="code"><?php esc_html_e( 'pi()', 'gp-advanced-calculations' ); ?></strong>
						</td>
						<td><?php esc_html_e( 'Returns pi (&pi;) to 20 decimal places.', 'gp-advanced-calculations' ); ?></td>
					</tr>
					<tr class="alternate">
						<td>
							<strong class="code"><?php esc_html_e( 'sqrt( value )', 'gp-advanced-calculations' ); ?></strong>
						</td>
						<td><?php esc_html_e( 'Returns square root of a number', 'gp-advanced-calculations' ); ?></td>
					</tr>
					</tbody>
				</table>

			</div>

		</div>
		<?php
	}

}

function gp_advanced_calculations() {
	return GP_Advanced_Calculations::get_instance();
}

GFAddon::register( 'GP_Advanced_Calculations' );

/*

Test Formulas

2 ^ 3 = 8
2 ^ ( 3 + 1 ) = 16
2 + 2 ^ 3 + 1 = 11
( 2 + 2 ) ^ ( 3 + 1 ) = 256
2 + ( 2 + 2 ) ^ ( 3 + 1 ) = 258
2 + ( 2 + 2 ) ^ ( 3 + 1 ) + 2

{Field Label:2} ^ 3 = 8
{Field Label:2} ^ ( 3 + 1 ) = 16
2 + {Field Label:2} ^ 3 + 1 = 11
( {Field Label:2} + 2 ) ^ ( 3 + 1 ) = 256
2 + ( {Field Label:2} + 2 ) ^ ( 3 + 1 ) = 258
2 + ( {Field Label:2} + 2 ) ^ ( 3 + 1 ) + 2

{Field Label:2} ^ {:3} = 8
{Field Label:2} ^ ( {:3} + 1 ) = 16
2 + {Field Label:2} ^ {:3} + 1 = 11
( {Field Label:2} + 2 ) ^ ( {:3} + 1 ) = 256
2 + ( {Field Label:2} + 2 ) ^ ( {:3} + 1 ) = 258
2 + ( {Field Label:2} + 2 ) ^ ( {:3} + 1 ) + 2

{Field Label:2}^3 = 8
{Field Label:2}^( 3 + 1 ) = 16
2 + {Field Label:2}^3 + 1 = 11
({Field Label:2}+2)^(3+1) = 256
2 + ({Field Label:2}+2 )^(3+1) = 258
2 + ({Field Label:2}+2 )^(3+1) + 2

( ( 1 ) + {Calculated Interest - FHA (Fixed):32} )


array(
	array(
		array(
			value => 1
		),
		array(
			value => +
		),
		array(
			value => {Calculated Interest - FHA (Fixed):32}
		)
	)
)


( {Total Loan Amount - FHA:25} * ( {Calculated Interest - FHA (Fixed):32} * ( 1 + {Calculated Interest - FHA (Fixed):32} ) ^ {Loan Term (Months) - FHA:27} ) / ( ( 1 + {Calculated Interest - FHA (Fixed):32} ) ^ {Loan Term (Months) - FHA:27} - 1 ) )

array(
	array(
		value => {Total Loan Amount - FHA:25}
	),
	array(
		value => *
	),
	array(
		array(

		)

*/
