<?php

/**
 * Pure discount calculation logic, kept free of WooCommerce cart/session code so it stays easy to test and reuse.
 *
 * @link       https://nudgesells.com
 * @since      1.0.0
 *
 * @package    NudgeSells
 * @subpackage NudgeSells/includes
 */

if (! defined('ABSPATH')) {
	exit;
}

/**
 * NudgeSells_Pricing_Engine
 *
 * Computes per-item, bundle, and tier discounts, and builds the customer-facing progress/summary messaging.
 *
 * @since      1.0.0
 * @package    NudgeSells
 * @subpackage NudgeSells/includes
 * @author     NudgeSells <support@nudgesells.com>
 */
class NudgeSells_Pricing_Engine
{

	/**
	 * Calculate.
	 *
	 * @param array $rules
	 * @param array $selected
	 *
	 * @return array
	 * @since 1.0.0
	 */
	public static function calculate(array $rules, array $selected)
	{
		$addon_rules_by_product = array();
		foreach ($rules['addons'] as $addon_rule) {
			$addon_rules_by_product[$addon_rule['product_id']] = $addon_rule;
		}

		$lines = array();
		$total_addon_qty = 0;

		foreach ($selected as $line_key => $line) {
			$product_id = $line['product_id'];
			$qty        = max(1, absint($line['qty']));
			$unit_price = floatval($line['unit_price']);

			$addon_rule = $addon_rules_by_product[$product_id] ?? null;

			$unit_after_addon = $unit_price;
			if ($addon_rule) {
				$unit_after_addon = self::apply_discount($unit_price, $addon_rule['discount_type'], $addon_rule['discount_value']);
			}

			$lines[$line_key] = array(
				'product_id'             => $product_id,
				'unit_price_original'    => $unit_price,
				'unit_price_after_addon' => $unit_after_addon,
				'unit_price_final'       => $unit_after_addon,
				'qty'                    => $qty,
			);

			$total_addon_qty += $qty;
		}

		$bundle_applied = false;
		if (! empty($rules['bundle_discount']['enabled']) && ! empty($rules['addons'])) {
			$selected_product_ids = wp_list_pluck($selected, 'product_id');
			$required_product_ids = wp_list_pluck($rules['addons'], 'product_id');
			$missing = array_diff($required_product_ids, $selected_product_ids);
			$bundle_applied = empty($missing);
		}

		$tier_applied = null;
		foreach ($rules['tiers'] as $tier) {
			if ($total_addon_qty >= $tier['min_qty']) {
				$tier_applied = $tier;
			}
		}

		$extra_discount_label = '';
		$use_bundle = false;
		$use_tier   = false;

		if ($bundle_applied && $tier_applied) {
			$bundle_is_free_reward = 'free_product' === $rules['bundle_discount']['type'];
			$tier_is_free_reward   = in_array($tier_applied['reward_shape'], array('free_units', 'free_all'), true);
			if ($bundle_is_free_reward && $tier_is_free_reward) {
				$use_bundle = true;
			} elseif ($bundle_is_free_reward) {
				$use_bundle = true;
			} elseif ($tier_is_free_reward) {
				$use_tier = true;
			} else {
				$sample_price = self::average_unit_price($lines);
				$bundle_off   = $sample_price - self::apply_discount($sample_price, $rules['bundle_discount']['type'], $rules['bundle_discount']['value']);
				$tier_off     = $sample_price - self::apply_discount($sample_price, $tier_applied['reward_shape'], $tier_applied['discount_value']);
				if ($bundle_off >= $tier_off) {
					$use_bundle = true;
				} else {
					$use_tier = true;
				}
			}
		} elseif ($bundle_applied) {
			$use_bundle = true;
		} elseif ($tier_applied) {
			$use_tier = true;
		}

		if ($use_bundle) {
			if ('free_product' === $rules['bundle_discount']['type']) {
				foreach ($lines as $key => $line) {
					if ((int) $line['product_id'] === (int) $rules['bundle_discount']['free_product_id']) {
						$lines[$key]['unit_price_final'] = 0.0;
					}
				}
			} else {
				foreach ($lines as $key => $line) {
					$lines[$key]['unit_price_final'] = self::apply_discount($line['unit_price_after_addon'], $rules['bundle_discount']['type'], $rules['bundle_discount']['value']);
				}
			}
			$extra_discount_label = ! empty($rules['bundle_discount']['label']) ? $rules['bundle_discount']['label'] : __('Bundle discount', 'nudgesells');
		} elseif ($use_tier) {
			self::apply_tier_reward($lines, $tier_applied, $total_addon_qty, ! empty($rules['tiers_repeat_scale']));
			$extra_discount_label = ! empty($tier_applied['label']) ? $tier_applied['label'] : __('Quantity discount', 'nudgesells');
		}

		foreach ($lines as $key => $line) {
			$lines[$key]['line_total_final'] = round($line['unit_price_final'] * $line['qty'], wc_get_price_decimals());
		}

		return array(
			'lines'                => $lines,
			'bundle_applied'       => $use_bundle,
			'bundle_complete'      => $bundle_applied,
			'tier_applied'         => $use_tier ? $tier_applied : null,
			'extra_discount_label' => $extra_discount_label,
			'total_addon_qty'      => $total_addon_qty,
		);
	}

	/**
	 * Format discount magnitude.
	 *
	 * @param mixed $type
	 * @param mixed $value
	 *
	 * @return mixed
	 * @since 1.0.0
	 */
	public static function format_discount_magnitude($type, $value)
	{
		$value = floatval($value);
		if ('percent' === $type) {
			return rtrim(rtrim(number_format($value, 2), '0'), '.') . '%';
		}
		return wc_price(NudgeSells_Currency::convert($value));
	}

	/**
	 * Build free reward progress message.
	 *
	 * Builds the "still working toward it" message for a tier whose
	 * reward_shape is free_units or free_all, covering all four
	 * target/shape combinations.
	 *
	 * @param array $tier
	 * @param int   $needed
	 *
	 * @return string
	 * @since 1.0.0
	 */
	public static function build_free_reward_progress_message(array $tier, $needed)
	{
		$specific  = 'specific_product' === $tier['reward_target'];
		$gift_name = '';
		if ($specific) {
			$gift_product = wc_get_product($tier['gift_product_id']);
			$gift_name    = $gift_product ? $gift_product->get_name() : __('gift', 'nudgesells');
		}

		if ('free_units' === $tier['reward_shape']) {
			$free_qty = absint($tier['free_qty']);
			if ($specific) {
				/* translators: 1: number of additional add-ons needed, 2: number of free units, 3: name of the free gift product */
				return sprintf(
					_n('Add %1$d more to get %2$d free %3$s!', 'Add %1$d more to get %2$d free %3$s!', $needed, 'nudgesells'),
					$needed,
					$free_qty,
					$gift_name
				);
			}
			/* translators: 1: number of additional add-ons needed, 2: number of free units */
			return sprintf(
				_n('Add %1$d more to get %2$d free!', 'Add %1$d more to get %2$d free!', $needed, 'nudgesells'),
				$needed,
				$free_qty
			);
		}

		if ($specific) {
			/* translators: 1: number of additional add-ons needed, 2: name of the free gift product */
			return sprintf(
				_n('Add %1$d more to get a free %2$s!', 'Add %1$d more to get a free %2$s!', $needed, 'nudgesells'),
				$needed,
				$gift_name
			);
		}
		/* translators: %d: number of additional add-ons needed */
		return sprintf(_n('Add %d more to get everything free!', 'Add %d more to get everything free!', $needed, 'nudgesells'), $needed);
	}

	/**
	 * Build free reward reached message.
	 *
	 * Builds the "reached" message for a tier whose reward_shape is
	 * free_units or free_all, covering all four target/shape
	 * combinations.
	 *
	 * @param array $tier
	 * @param int   $total_addon_qty Needed to reflect the repeat-scaled amount, if enabled.
	 * @param bool  $repeat_scale    Offer-level setting.
	 *
	 * @return string
	 * @since 1.0.0
	 */
	public static function build_free_reward_reached_message(array $tier, $total_addon_qty = 0, $repeat_scale = false)
	{
		$specific  = 'specific_product' === $tier['reward_target'];
		$gift_name = '';
		if ($specific) {
			$gift_product = wc_get_product($tier['gift_product_id']);
			$gift_name    = $gift_product ? $gift_product->get_name() : __('gift', 'nudgesells');
		}

		if ('free_units' === $tier['reward_shape']) {
			$free_qty = self::effective_free_qty($tier, $total_addon_qty, $repeat_scale);
			if ($specific) {
				/* translators: 1: number of free units, 2: name of the free gift product */
				return sprintf(_n('You\'ve unlocked %1$d free %2$s!', 'You\'ve unlocked %1$d free %2$s!', $free_qty, 'nudgesells'), $free_qty, $gift_name);
			}
			/* translators: %d: number of free units */
			return sprintf(_n('You\'ve unlocked %d free!', 'You\'ve unlocked %d free!', $free_qty, 'nudgesells'), $free_qty);
		}

		if ($specific) {
			/* translators: %s: name of the free gift product */
			return sprintf(__('You\'ve unlocked a free %s!', 'nudgesells'), $gift_name);
		}
		return __('Everything is free!', 'nudgesells');
	}

	/**
	 * Format savings label.
	 *
	 * @param mixed $type
	 * @param mixed $value
	 *
	 * @return mixed
	 * @since 1.0.0
	 */
	public static function format_savings_label($type, $value)
	{
		if (floatval($value) <= 0) {
			return '';
		}
		if ('fixed_price' === $type) {
			/* translators: %s: the new fixed price, e.g. "$9.99" */
			return sprintf(__('Now %s', 'nudgesells'), self::format_discount_magnitude($type, $value));
		}
		/* translators: %s: discount magnitude, e.g. "20%" or "$10.00" */
		return sprintf(__('Save %s', 'nudgesells'), self::format_discount_magnitude($type, $value));
	}

	/**
	 * Build the bundle/tier-specific discount message together with
	 * whether it represents a "reached" (complete) state - computed
	 * together from the same branch, rather than as two separate methods
	 * that could disagree on an edge case (e.g. a offer with both a
	 * bundle and tiers configured). For tiers, "reached" is true as soon
	 * as the first tier is satisfied, even while still progressing
	 * toward a higher one - a discount is already active at that point.
	 *
	 * @param array $rules
	 * @param array $result
	 *
	 * @return array{message: string, reached: bool}
	 * @since 1.0.0
	 */
	public static function evaluate_discount_message(array $rules, array $result)
	{
		$bundle = $rules['bundle_discount'];
		$bundle_is_meaningful = 'free_product' === $bundle['type']
			? $bundle['free_product_id'] > 0
			: floatval($bundle['value']) > 0;

		if (! empty($bundle['enabled']) && $bundle_is_meaningful && ! empty($rules['addons'])) {
			$free_product_name = '';
			if ('free_product' === $bundle['type']) {
				$free_product = wc_get_product($bundle['free_product_id']);
				$free_product_name = $free_product ? $free_product->get_name() : __('gift', 'nudgesells');
			}

			if (! empty($result['bundle_complete'])) {
				if (! empty($bundle['label'])) {
					return array(
						'message' => str_replace('{free_product}', $free_product_name, $bundle['label']),
						'reached' => true,
					);
				}
				if ('free_product' === $bundle['type']) {
					/* translators: %s: name of the free product */
					return array(
						'message' => sprintf(__('You\'ve unlocked a free %s!', 'nudgesells'), $free_product_name),
						'reached' => true,
					);
				}
				return array(
					'message' => __('Bundle discount unlocked!', 'nudgesells'),
					'reached' => true,
				);
			}

			$selected_product_ids = wp_list_pluck($result['lines'], 'product_id');
			$required_product_ids = wp_list_pluck($rules['addons'], 'product_id');
			$missing               = count(array_diff($required_product_ids, $selected_product_ids));

			if ($missing > 0) {
				if (! empty($bundle['progress_label'])) {
					$magnitude = 'free_product' === $bundle['type'] ? '' : self::format_discount_magnitude($bundle['type'], $bundle['value']);
					return array(
						'message' => str_replace(
							array('{missing}', '{discount}', '{free_product}'),
							array($missing, $magnitude, $free_product_name),
							$bundle['progress_label']
						),
						'reached' => false,
					);
				}
				if ('free_product' === $bundle['type']) {
					return array(
						'message' => sprintf(
							/* translators: 1: number of additional add-ons needed, 2: name of the free product */
							_n('Add %1$d more to unlock a free %2$s', 'Add %1$d more to unlock a free %2$s', $missing, 'nudgesells'),
							$missing,
							$free_product_name
						),
						'reached' => false,
					);
				}
				return array(
					'message' => sprintf(
						/* translators: %d: number of additional add-ons needed to unlock the bundle discount */
						_n('Add %d more to unlock bundle discount', 'Add %d more to unlock bundle discount', $missing, 'nudgesells'),
						$missing
					),
					'reached' => false,
				);
			}
		}

		$meaningful_tiers = array_values(array_filter($rules['tiers'], function ($tier) {
			if ('free_all' === $tier['reward_shape']) {
				return 'specific_product' !== $tier['reward_target'] || $tier['gift_product_id'] > 0;
			}
			if ('free_units' === $tier['reward_shape']) {
				if ('specific_product' === $tier['reward_target'] && ! $tier['gift_product_id']) {
					return false;
				}
				return $tier['free_qty'] > 0;
			}
			return floatval($tier['discount_value']) > 0;
		}));

		if (! empty($meaningful_tiers)) {
			$qty          = $result['total_addon_qty'];
			$current_tier = null;
			$next_tier    = null;

			foreach ($meaningful_tiers as $tier) {
				if ($qty >= $tier['min_qty']) {
					$current_tier = $tier;
				} elseif (null === $next_tier) {
					$next_tier = $tier;
				}
			}

			if ($next_tier) {
				$needed          = $next_tier['min_qty'] - $qty;
				$already_reached = null !== $current_tier;
				if (! empty($next_tier['label'])) {
					$tier_free_product_name = '';
					if ('specific_product' === $next_tier['reward_target'] && $next_tier['gift_product_id']) {
						$tier_free_product = wc_get_product($next_tier['gift_product_id']);
						$tier_free_product_name = $tier_free_product ? $tier_free_product->get_name() : '';
					}
					return array(
						'message' => str_replace(
							array('{qty}', '{free_product}'),
							array($needed, $tier_free_product_name),
							$next_tier['label']
						),
						'reached' => $already_reached,
					);
				}
				if (in_array($next_tier['reward_shape'], array('free_units', 'free_all'), true)) {
					return array(
						'message' => self::build_free_reward_progress_message($next_tier, $needed),
						'reached' => $already_reached,
					);
				}
				$magnitude = self::format_discount_magnitude($next_tier['reward_shape'], $next_tier['discount_value']);
				if ('fixed_price' === $next_tier['reward_shape']) {
					return array(
						'message' => sprintf(
							/* translators: 1: number of additional add-ons needed, 2: the new fixed price e.g. "$9.99" */
							_n('Add %1$d more to unlock a price of %2$s', 'Add %1$d more to unlock a price of %2$s', $needed, 'nudgesells'),
							$needed,
							$magnitude
						),
						'reached' => $already_reached,
					);
				}
				return array(
					'message' => sprintf(
						/* translators: 1: number of additional add-ons needed, 2: discount magnitude e.g. "20%" */
						_n('Add %1$d more to get another %2$s off', 'Add %1$d more to get another %2$s off', $needed, 'nudgesells'),
						$needed,
						$magnitude
					),
					'reached' => $already_reached,
				);
			}

			if ($current_tier) {
				if (! empty($rules['tier_final_message'])) {
					$tier_free_product_name = '';
					if ('specific_product' === $current_tier['reward_target'] && $current_tier['gift_product_id']) {
						$tier_free_product = wc_get_product($current_tier['gift_product_id']);
						$tier_free_product_name = $tier_free_product ? $tier_free_product->get_name() : '';
					}
					return array(
						'message' => str_replace(
							array('{qty}', '{free_product}'),
							array($qty, $tier_free_product_name),
							$rules['tier_final_message']
						),
						'reached' => true,
					);
				}
				if (in_array($current_tier['reward_shape'], array('free_units', 'free_all'), true)) {
					return array(
						'message' => self::build_free_reward_reached_message($current_tier, $qty, ! empty($rules['tiers_repeat_scale'])),
						'reached' => true,
					);
				}
				$magnitude = self::format_discount_magnitude($current_tier['reward_shape'], $current_tier['discount_value']);
				if ('fixed_price' === $current_tier['reward_shape']) {
					/* translators: %s: the new fixed price, e.g. "$9.99" */
					return array(
						'message' => sprintf(__('Now %s!', 'nudgesells'), $magnitude),
						'reached' => true,
					);
				}
				return array(
					/* translators: %s: discount magnitude, e.g. "20%" */
					'message' => sprintf(__('You save %s!', 'nudgesells'), $magnitude),
					'reached' => true,
				);
			}
		}

		return array(
			'message' => '',
			'reached' => false,
		);
	}

	/**
	 * Build the generic "you save $X" savings message - always the plain
	 * total-saved amount, regardless of any bundle/tier-specific
	 * messaging (which lives in evaluate_discount_message() instead).
	 *
	 * @param array $rules
	 * @param array $result
	 *
	 * @return string
	 * @since 1.0.0
	 */
	public static function build_savings_note(array $rules, array $result)
	{
		if (! empty($result['total_saved']) && $result['total_saved'] > 0) {
			/* translators: %s: formatted money amount */
			return sprintf(__('You save %s!', 'nudgesells'), wc_price($result['total_saved']));
		}

		return '';
	}

	/**
	 * Resolve effective prices.
	 *
	 * @param WC_Product $product
	 * @param mixed $discount_type
	 * @param mixed $discount_value
	 *
	 * @return array
	 * @since 1.0.0
	 */
	public static function resolve_effective_prices(WC_Product $product, $discount_type, $discount_value)
	{
		$regular    = (float) $product->get_regular_price();
		if (! $regular) {
			$regular = (float) $product->get_price();
		}
		$base_price = $regular;

		if (floatval($discount_value) > 0) {
			$ignore_sale_price = 'yes' === NudgeSells_Admin_Settings::get('ignore_sale_price_for_discounts', 'yes');
			$base_price = $ignore_sale_price ? $regular : (float) $product->get_price();
			$final = self::apply_discount($base_price, $discount_type, $discount_value);
		} elseif ($product->is_on_sale()) {
			$final = (float) $product->get_sale_price();
		} else {
			$final = $regular;
		}

		return array(
			'original' => $regular,
			'base'     => $base_price,
			'final'    => $final,
		);
	}

	/**
	 * Apply discount.
	 *
	 * @param mixed $price
	 * @param mixed $type
	 * @param mixed $value
	 *
	 * @return mixed
	 * @since 1.0.0
	 */
	public static function apply_discount($price, $type, $value)
	{
		$price = floatval($price);
		$value = floatval($value);
		if ($value <= 0) {
			return $price;
		}
		if ('percent' === $type) {
			$final = $price * (1 - ($value / 100));
		} elseif ('fixed_price' === $type) {
			// The value IS the new price, not an amount to subtract --
			// still run through currency conversion since it's an
			// absolute money amount the admin configured, same as 'fixed'.
			$final = NudgeSells_Currency::convert($value);
		} else {

			$final = $price - NudgeSells_Currency::convert($value);
		}
		return max(0, round($final, wc_get_price_decimals()));
	}

	/**
	 * Average unit price.
	 *
	 * @param mixed $lines
	 *
	 * @return mixed
	 * @since 1.0.0
	 */
	private static function average_unit_price($lines)
	{
		if (empty($lines)) {
			return 0;
		}
		$total = 0;
		foreach ($lines as $line) {
			$total += $line['unit_price_after_addon'];
		}
		return $total / count($lines);
	}

	/**
	 * Apply tier reward.
	 *
	 * Applies a reached tier's reward to $lines in place, based on its
	 * reward_target (all_selected / specific_product) crossed with its
	 * reward_shape (percent / fixed / free_units / free_all).
	 *
	 * @param array $lines           Modified in place.
	 * @param array $tier
	 * @param int   $total_addon_qty Needed to compute the repeat-scale multiplier.
	 * @param bool  $repeat_scale    Offer-level setting -- applies to whichever tier is reached, not configured per-tier.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	private static function apply_tier_reward(array &$lines, array $tier, $total_addon_qty = 0, $repeat_scale = false)
	{
		$target = $tier['reward_target'];
		$shape  = $tier['reward_shape'];

		if ('specific_product' === $target) {
			foreach ($lines as $key => $line) {
				if ((int) $line['product_id'] !== (int) $tier['gift_product_id']) {
					continue;
				}
				if ('free_units' === $shape) {
					$effective_free_qty = self::effective_free_qty($tier, $total_addon_qty, $repeat_scale);
					self::apply_free_units_to_line($lines, $key, $effective_free_qty);
				} else {
					$lines[$key]['unit_price_final'] = 0.0;
				}
			}
			return;
		}

		if ('free_units' === $shape) {
			$effective_free_qty = self::effective_free_qty($tier, $total_addon_qty, $repeat_scale);
			self::distribute_free_units($lines, $effective_free_qty);
			return;
		}
		if ('free_all' === $shape) {
			foreach ($lines as $key => $line) {
				$lines[$key]['unit_price_final'] = 0.0;
			}
			return;
		}
		foreach ($lines as $key => $line) {
			$lines[$key]['unit_price_final'] = self::apply_discount($line['unit_price_after_addon'], $shape, $tier['discount_value']);
		}
	}

	/**
	 * Effective free qty.
	 *
	 * Returns how many free units a free_units tier is actually worth
	 * right now. Without repeat_scale, this is always just the
	 * configured free_qty. With repeat_scale on, it's free_qty
	 * multiplied by how many complete multiples of the tier's own
	 * min_qty have been assembled -- e.g. a "min_qty=2, free_qty=1"
	 * tier (buy 1 get 1) at a total of 4 selected becomes worth 2 free
	 * units (buy 2 get 2), since two full "buy 1 get 1" cycles fit.
	 *
	 * @param array $tier
	 * @param int   $total_addon_qty
	 *
	 * @return int
	 * @since 1.0.0
	 */
	private static function effective_free_qty(array $tier, $total_addon_qty, $repeat_scale = false)
	{
		$free_qty = absint($tier['free_qty']);
		if (! $repeat_scale || $tier['min_qty'] <= 0) {
			return $free_qty;
		}
		$cycles = (int) floor($total_addon_qty / $tier['min_qty']);
		return $free_qty * max(1, $cycles);
	}

	/**
	 * Distribute free units.
	 *
	 * Spends a pool of "free units" across $lines in order, up to each
	 * line's own quantity, until the pool runs out. This is what makes
	 * "buy 5 get 2 free" work: if only one product is selected, all the
	 * free units land on it; with several selected, they're consumed in
	 * order across them. A line with N free units out of its qty is
	 * represented as an averaged per-unit price -- ((qty - free) *
	 * unit_price) / qty -- so it still fits the existing unit_price_final
	 * * qty = line_total_final calculation without needing to split a
	 * line's quantity into two separate cart-priced sub-lines.
	 *
	 * @param array $lines    Modified in place.
	 * @param int   $free_qty
	 *
	 * @return void
	 * @since 1.0.0
	 */
	private static function distribute_free_units(array &$lines, $free_qty)
	{
		$remaining = absint($free_qty);
		foreach ($lines as $key => $line) {
			if ($remaining <= 0) {
				break;
			}
			$free_here = self::apply_free_units_to_line($lines, $key, $remaining);
			$remaining -= $free_here;
		}
	}

	/**
	 * Apply free units to line.
	 *
	 * Makes up to $free_qty units of a single line free (never more than
	 * the line's own quantity), returning how many units were actually
	 * used so callers distributing across multiple lines know how much
	 * remains in the pool.
	 *
	 * @param array $lines    Modified in place.
	 * @param mixed $key
	 * @param int   $free_qty
	 *
	 * @return int
	 * @since 1.0.0
	 */
	private static function apply_free_units_to_line(array &$lines, $key, $free_qty)
	{
		$line = $lines[$key];
		if ($line['qty'] <= 0) {
			return 0;
		}
		$free_here  = min(absint($free_qty), $line['qty']);
		$paid_units = $line['qty'] - $free_here;
		$lines[$key]['unit_price_final'] = ($paid_units * $line['unit_price_after_addon']) / $line['qty'];
		return $free_here;
	}

	/**
	 * Calculate upgrade price.
	 *
	 * @param mixed $upgrade_rule
	 * @param mixed $upgrade_product_price
	 *
	 * @return mixed
	 * @since 1.0.0
	 */
	public static function calculate_upgrade_price($upgrade_rule, $upgrade_product_price)
	{
		return self::apply_discount($upgrade_product_price, $upgrade_rule['discount_type'], $upgrade_rule['discount_value']);
	}
}
