<?php

namespace App\Models;

use Exception;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

use App\Classes\FTN\Packet;
use App\Http\Controllers\DomainController;
use App\Traits\ScopeActive;

class Address extends Model
{
	use ScopeActive,SoftDeletes;

	/* SCOPES */

	public function scopeFTNOrder($query)
	{
		return $query
			->orderBy('region_id')
			->orderBy('host_id')
			->orderBy('node_id')
			->orderBy('point_id');
	}

	/* RELATIONS */

	/**
	 * Find children dependant on this record
	 */
	public function children()
	{
		switch (strtolower($this->role)) {
			case 'region':
				return $this->hasMany(self::class,'region_id','region_id')
					->where('zone_id',$this->zone_id)
					->where(function($q) {
						return $q->where('host_id',0)
							->orWhere('role',DomainController::NODE_NC);
					})
					->where('id','<>',$this->id);

			case 'host':
				return $this->hasMany(self::class,'host_id','host_id')
					->where('zone_id',$this->zone_id)
					->where('region_id',$this->region_id)
					->whereNull('hub_id')
					->where('id','<>',$this->id);

			case 'hub':
				return $this->hasMany(self::class,'hub_id','id');

			case 'node':
				return NULL;

			default:
				throw new Exception('Unknown role: '.$this->role);
		}
	}

	public function system()
	{
		return $this->belongsTo(System::class);
	}

	public function zone()
	{
		return $this->belongsTo(Zone::class);
	}

	/* ATTRIBUTES */

	/**
	 * Render the node name in full 5D
	 *
	 * @return string
	 */
	public function getFTNAttribute(): string
	{
		return sprintf('%s@%s',$this->getFTN4DAttribute(),$this->zone->domain->name);
	}

	public function getFTN3DAttribute(): string
	{
		return sprintf('%d:%d/%d',$this->zone->zone_id,$this->host_id ?: $this->region_id,$this->node_id);
	}

	public function getFTN4DAttribute(): string
	{
		return sprintf('%s.%d',$this->getFTN3DAttribute(),$this->point_id);
	}

	public function getRoleAttribute($value)
	{
		switch ($value) {
			case DomainController::NODE_ZC;
				return 'Zone';
			case DomainController::NODE_RC;
				return 'Region';
			case DomainController::NODE_NC;
				return 'Host';
			case DomainController::NODE_HC;
				return 'Hub';
			case DomainController::NODE_PVT;
				return 'PVT';
			case DomainController::NODE_DOWN;
				return 'DOWN';
			case NULL:
				return 'Node';
			default:
				return '?';
		}
	}

	/* METHODS */

	/**
	 * Find a record in the DB for a node string, eg: 10:1/1.0
	 *
	 * @param string $ftn
	 * @return Address|null
	 * @throws Exception
	 */
	public static function findFTN(string $ftn): ?self
	{
		$ftn = self::parseFTN($ftn);

		$o = (new self)->active()
			->select('addresses.*')
			->where('zones.zone_id',$ftn['z'])
			->where('host_id',$ftn['n'])
			->join('zones',['zones.id'=>'addresses.zone_id'])
			->join('domains',['domains.id'=>'zones.domain_id'])
			->where('zones.active',TRUE)
			->where('domains.active',TRUE)
			->where('addresses.active',TRUE)
			->where('node_id',$ftn['f'])
			->where('point_id',$ftn['p'])
			->when($ftn['d'],function($query,$domain) {
				$query->where('domains.name',$domain);
			})
			->when((! $ftn['d']),function($query) {
				$query->where('domains.default',TRUE);
			})
			->single();

		return ($o && $o->system->active) ? $o : NULL;
	}

	/**
	 * Get netmail for this node (including it's children)
	 */
	public function getNetmail(): Packet
	{
		$o = new Packet($this);

		foreach (Netmail::whereIn('tftn_id',$this->children->pluck('id')->push($this->id))->get() as $oo) {
			$o->addNetmail($oo->packet());

			// @todo We need to mark the netmail as sent
		}

		return $o;
	}

	/**
	 * Parse a string and split it out as an FTN array
	 *
	 * @param string $ftn
	 * @return array
	 * @throws Exception
	 */
	public static function parseFTN(string $ftn): array
	{
		// http://ftsc.org/docs/frl-1028.002
		if (! preg_match('#^([0-9]+):([0-9]+)/([0-9]+)(.([0-9]+))?(@([a-z0-9\-_~]{0,8}))?$#',strtolower($ftn),$matches))
			throw new Exception('Invalid FTN: '.$ftn);

		// Check our numbers are correct.
		foreach ([1,2,3] as $i) {
			if ((! is_numeric($matches[$i])) || ($matches[$i] > DomainController::NUMBER_MAX))
				throw new Exception('Invalid FTN: '.$ftn);
		}

		if (isset($matches[5]) AND ((! is_numeric($matches[$i])) || ($matches[5] > DomainController::NUMBER_MAX)))
			throw new Exception('Invalid FTN: '.$ftn);

		return [
			'z'=>(int)$matches[1],
			'n'=>(int)$matches[2],
			'f'=>(int)$matches[3],
			'p'=>isset($matches[5]) && $matches[5] ? (int)$matches[5] : 0,
			'd'=>$matches[7] ?? NULL
		];
	}

	/**
	 * Retrieve the address session details/passwords
	 *
	 * @param string $type
	 * @return string|null
	 */
	public function session(string $type): ?string
	{
		static $session = NULL;

		if (is_null($session)) {
			$session = (new SystemZone)
				->where('zone_id',$this->zone_id)
				->where('system_id',$this->system_id)
				->single();
		}

		return $session ? $session->{$type} : NULL;
	}
}