<?php

namespace App\Models\Supplier;

use Illuminate\Support\Arr;
use Illuminate\Support\Collection;

use App\Models\Product\Broadband as ProductBroadband;

class Broadband extends Type
{
	protected const category_name = 'Broadband';

	protected $table = 'supplier_broadband';

	// The model of the product that is supplied by this model
	const ProductModel = ProductBroadband::class;

	protected $casts = [
		'offpeak_start' => 'datetime:H:i',
		'offpeak_end' => 'datetime:H:i',
	];

	// Map the table fields, with the extra fields
	public const traffic_map = [
		'base_up_offpeak' => 'extra_up_offpeak',
		'base_down_offpeak' => 'extra_down_offpeak',
		'base_up_peak' => 'extra_up_peak',
		'base_down_peak' => 'extra_down_peak',
	];

	// Map the NULL relationships - and where traffic gets applied if NULL
	public const traffic_merge = [
		'extra_up_offpeak' => 'base_down_offpeak',
		'extra_down_offpeak' => 'base_down_peak',
		'extra_up_peak' => 'base_down_peak',
		'extra_down_peak' => 'base_down_peak',
	];

	/* METHODS */

	/**
	 * Determine how traffic is counted for Broadband links.
	 *
	 * Configuration allows for traffic to fit into the following categories:
	 *   + down_peak, when not NULL, traffic is included to value of this metric, extra traffic is charged per extra_peak
	 *   + down_offpeak, when not NULL, traffic is included to the value of metric, extra traffic is charged per extra_offpeak
	 *
	 *     If:
	 *   + UPLOADS are charged and there are no PEAK/OFFPEAK periods (therefore all
	 *     traffic is charged), the allowance will be shown as 1 metric - TRAFFIC.
	 *   + UPLOADS are charged and there are PEAK/OFFPEAK periods the allowance
	 *     will be shown as 2 metrics - PEAK/OFFPEAK.
	 *   + UPLOADS are NOT charged and there are no PEAK/OFFPEAK periods the allowance
	 *     will be shown as 1 metrics - TRAFFIC.
	 *   + UPLOADS are NOT charged and there are PEAK/OFFPEAK periods the allowance
	 *     will be shown as 2 metrics - PEAK/OFFPEAK.
	 *
	 * Thus:
	 *   (x = up/down, Y=peak/offpeak)
	 *
	 *   + If base_x_Y is NULL, all Y traffic is FREE (ignore respective extra_x_Y setting).
	 *   + If base_x_Y is a number, all Y traffic is FREE up to the number (evaluate extra_x_Y setting).
	 *   + If extra_x_Y is a number, charge this amount for traffic over base_x_Y.
	 *
	 *   + If extra_down_peak is NULL this is invalid, treat base_down_peak as NULL
	 *   + If extra_down_offpeak is NULL add traffic_down_offpeak to traffic_down_peak
	 *   + If extra_up_peak is NULL add traffic_up_peak to traffic_down_peak
	 *   + If extra_up_offpeak is NULL add traffic_up_offpeak to traffic_down_offpeak
	 *
	 * @param Collection|null $config The configuration of the link, if NULL assume the supplieres configuration
	 * @param array $data The traffic used on this link, determine whats left or over
	 * @param bool $ceil Round the numbers to integers
	 * @return array|string
	 */
	public function allowance(?Collection $config=NULL,array $data=[],bool $ceil=TRUE)
	{
		$map = collect(self::traffic_map);
		$merge = collect(self::traffic_merge);

		if (is_null($config))
			$config = collect($config);

		// If config is null, use the configuration from this Model
		if (! $config->count()) {
			// Base Config
			foreach ($map->keys() as $k)
				$config->put($k,$this->{$k});

			// Excess Config
			foreach ($map->values() as $k)
				$config->put($k,$this->{$k});

			// Shaped or Charge
			$config->put('shaped',$this->extra_shaped);
			$config->put('charged',$this->extra_charged);

			// Metric - used to round down data in $data.
			$config->put('metric',$this->metric);
		}

		$result = collect();

		// If data is empty, we'll report on allowance, otherwise we'll report on consumption
		$report = ! $data;

		// Work out if we charge each period
		foreach ($map as $k => $v) {
			// Anything NULL is not counted
			if (is_null($config->get($k)))
				continue;

			$x = $report ? $config->get($k) : ($config->get($k)-Arr::get($data,$k,0));

			if ($ceil)
				$x = (int)ceil($x);

			// Non-NULL entries are counted as is
			if (! is_null($config->get($v))) {
				// Existing value for this item to be added
				$value = $result->has($k) ? $result->get($k) : 0;
				$result->put($k,$value+$x);

			// NULL entries are merged into another key
			} else {
				// New Key for this item
				$key = $merge->get($v);

				// Existing value for this item to be added
				$value = $result->has($key) ? $result->get($key) : 0;

				// Any value in the existing key, add it too.
				if ($k !== $key AND $result->has($k)) {
					$value += $result->get($k);
					$result->forget($k);
				}

				$result->put($key,$value+$x);
			}
		}

		if ($config->has('metric') AND $config->get('metric'))
			$result->map(function($item) use ($config,$ceil) {
				return $ceil
					? (int)ceil($item/$config->get('metric'))
					: $item/$config->get('metric');
			});

		return $result;
	}

	public function validation(): array
	{
		// @todo Enhance the validation so that extra_* values are not accepted if base_* values are not included.
		return array_merge(parent::validation(),
			[
				'extra_shaped' => 'sometimes|accepted',
				'extra_charged' => 'sometimes|accepted',
				'metric' => 'nullable|numeric|min:1',
				'speed' => 'nullable|string|max:64',
				'technology' => 'nullable|string|max:255',
				'offpeak_start' => 'nullable|date_format:H:i',
				'offpeak_end' => 'nullable|date_format:H:i',
				'base_down_peak' => 'nullable|numeric',
				'base_up_peak' => 'nullable|numeric',
				'base_down_offpeak' => 'nullable|numeric',
				'base_up_offpeak' => 'nullable|numeric',
				'extra_down_peak' => 'nullable|numeric',
				'extra_up_peak' => 'nullable|numeric',
				'extra_down_offpeak' => 'nullable|numeric',
				'extra_up_offpeak' => 'nullable|numeric',
			]);
	}
}