<?php

namespace App\Classes;

use Carbon\Carbon;

use App\Exceptions\InvalidFidoPacketException;
use App\Traits\GetNode;

class FTNPacket extends FTN
{
	use GetNode;

	public $pktsrc = NULL;
	public $pktdst = NULL;
	private $pktver = NULL;
	public $date = NULL;
	private $baud = NULL;
	private $software = [];
	private $cap = [];
	private $proddata = NULL;
	private $password = NULL;

	private $sz = NULL;
	private $dz = NULL;
	private $sn = NULL;
	private $dn = NULL;
	private $sf = NULL;
	private $df = NULL;
	private $sp = NULL;
	private $dp = NULL;

	public $filename = NULL;
	public $messages = NULL;

	// First part of header
	private $pack1 = [
		'onode'=>[0x00,'v',2],
		'dnode'=>[0x02,'v',2],
		'y'=>[0x04,'v',2],
		'm'=>[0x06,'v',2],
		'd'=>[0x08,'v',2],
		'H'=>[0x0a,'v',2],
		'M'=>[0x0c,'v',2],
		'S'=>[0x0e,'v',2],
		'baud'=>[0x10,'v',2],
		'pktver'=>[0x12,'v',2],
		'onet'=>[0x14,'v',2],
		'dnet'=>[0x16,'v',2],
		'prodcode-lo'=>[0x18,'C',1],
		'prodrev-maj'=>[0x19,'C',1],
		];

	// Second part of header
	private $pack2 = [
		'qozone'=>[0x22,'v',2],
		'qdzone'=>[0x24,'v',2],
		'filler'=>[0x26,'v',2],
		'capvalid'=>[0x28,'v',2],
		'prodcode-hi'=>[0x2a,'C',1],
		'prodrev-min'=>[0x2b,'C',1],
		'capword'=>[0x2c,'v',1],
		'ozone'=>[0x2e,'v',2],
		'dzone'=>[0x30,'v',2],
		'opoint'=>[0x32,'v',2],
		'dpoint'=>[0x34,'v',2],
		];

	public function __construct(string $file=NULL)
	{
		$this->messages = collect();

		if ($file) {
			$this->filename = $file;

			return $this->OpenFile($file);
		}
	}

	public function __get($k)
	{
		switch ($k)
		{
			case 'fz': return ftn_address_split($this->pktsrc,'z');
			case 'fn': return ftn_address_split($this->pktsrc,'n');
			case 'ff': return ftn_address_split($this->pktsrc,'f');
			case 'fp': return ftn_address_split($this->pktsrc,'p');

			case 'tz': return ftn_address_split($this->pktdst,'z');
			case 'tn': return ftn_address_split($this->pktdst,'n');
			case 'tf': return ftn_address_split($this->pktdst,'f');
			case 'tp': return ftn_address_split($this->pktdst,'p');

			default:
				return isset($this->{$k}) ? $this->{$k} : NULL;
		}
	}

	// @note - messages in this object have the same next destination
	public function __toString(): string
	{
		// @todo - is this appropriate to set here
		$this->date = now();
		$this->pktsrc = (string)$this->get_node(ftn_address_split('10:1/5.0'),TRUE);

		// @todo
		if ($this->messages->first()->type == 'echomail')
			$this->pktdst = (string)$this->messages->first()->fqfa->uplink;
		else
			$this->pktdst = (string)$this->messages->first()->fqda->uplink;

		$this->software['prodcode-lo'] = 0x00;
		$this->software['prodcode-hi'] = 0xde;
		$this->software['rev-maj'] = 0x00;
		$this->software['rev-min'] = 0x01;

		// Type 2+ Packet
		$this->cap['valid'] = 0x0100;
		$this->cap['word'] = 0x0001;
		$this->pktver = 0x0002;

		$return = $this->createHeader();

		foreach ($this->messages as $o)
			$return .= "\02\00".(string)$o;

		$return .= "\00\00";

		return $return;
	}

	/**
	 * Create our message packet header
	 */
	private function createHeader(): string
	{
		try {
			$a = pack(join('',collect($this->pack1)->pluck(1)->toArray()),
				$this->ff,
				$this->tf,
				$this->date->year,
				$this->date->month,
				$this->date->day,
				$this->date->hour,
				$this->date->minute,
				$this->date->second,
				$this->baud,
				$this->pktver,
				$this->fn,                       // @todo if point, this needs to be 0xff
				$this->tn,
				$this->software['prodcode-lo'],  // @todo change to this software
				$this->software['rev-maj']       // @todo change to this software
				);

			$b = pack(join('',collect($this->pack2)->pluck(1)->toArray()),
				0x0000,                          // @note: Type 2 packet this is $this->sz,
				0x0000,                          // @note: Type 2 packet this is $this->dz,
				0x0000,                          // Filler $this->>sn if message to point.
				$this->cap['valid'],             // @todo to check
				$this->software['prodcode-hi'],  // @todo change to this software
				$this->software['rev-min'],      // @todo change to this software
				$this->cap['word'],              // @todo to check
				$this->fz,
				$this->tz,
				$this->fp,                       // @note: point address, type 2+ packets
				$this->tp                        // @note: point address, type 2+ packets
			);

			return $a.pack('a8',strtoupper($this->password)).$b."mbse"; // @todo change to this software

		} catch (\Exception $e) {
			return $e->getMessage();
		}
	}

	public function addMessage(FTNMessage $o)
	{
		// @todo Check that this message is for the same uplink.
		$this->messages->push($o);
	}

	public function dump()
	{
		return hex_dump((string)$this);
	}

	/**
	 * Open a packet file
	 *
	 * @param string $file
	 * @throws InvalidFidoPacketException
	 */
	private function OpenFile(string $file)
	{
		$f = fopen($file,'r');
//		$fstat = fstat($f);

		// PKT Header
		$header = fread($f,0x3a);

		// Could not read header
		if (strlen($header) != 0x3a)
			throw new InvalidFidoPacketException('Length of Header too short: '.$file);

		// Not a type 2 packet
		if (array_get(unpack('vv',substr($header,0x12)),'v') != 2)
			throw new InvalidFidoPacketException('Not a type 2 packet in file: '. $file);

		$this->parseHeader($header);

		while (! feof($f))
		{
			$x = fread($f,2);

			// End of Packet?
			if (strlen($x) == 2 and $x == "\00\00")
			{
				break;
			}

			// Messages start with 02H 00H
			if (strlen($x) == 2 AND $x != "\02\00")
				throw new InvalidFidoPacketException('Not a valid packet: '.bin2hex($x));

			// No message attached
			else if (! strlen($x))
				break;

			$message = new FTNMessage(fread($f,0xc));
			$message->date = Carbon::createFromFormat('d M y H:i:s',$this->readnullfield($f));
			$message->to = $this->readnullfield($f);
			$message->from = $this->readnullfield($f);
			$message->subject = $this->readnullfield($f);

			$message->parsemessage($this->readnullfield($f));

			$this->messages->push($message);
		}
	}

	private function readnullfield($f)
	{
		$result = '';

		while (($x = fgetc($f) OR strlen($x)) AND $x !== "\00")
		{
			$result .= $x;
		}

		return $result;
	}

	private function parseHeader(string $header)
	{
		$result1 = unpack($this->unpackheader($this->pack1),substr($header,0,0x1a));
		$this->password = array_get(unpack('a*p',substr($header,0x1a,8)),'p');
		$result2 = unpack($this->unpackheader($this->pack2),substr($header,0x22,0x14));
		$this->proddata = array_get(unpack('A*p',substr($header,0x36,4)),'p');

		// @todo replcae these vars with the tz/fz
		$this->sz = array_get($result2,'ozone');
		$this->sn = array_get($result1,'onet');
		$this->sf = array_get($result1,'onode');
		$this->sp = array_get($result2,'dpoint');
		$this->pktsrc = sprintf('%s:%s/%s.%s',
			$this->sz,
			$this->sn,
			$this->sf,
			$this->sp
			);

		$this->dz = array_get($result2,'dzone');
		$this->dn = array_get($result1,'dnet');
		$this->df = array_get($result1,'dnode');
		$this->dp = array_get($result2,'dpoint');
		$this->pktdst = sprintf('%s:%s/%s.%s',
			$this->dz,
			$this->dn,
			$this->df,
			$this->dp
		);

		$this->date = Carbon::create(
			array_get($result1,'y'),
			array_get($result1,'m'),
			array_get($result1,'d'),
			array_get($result1,'H'),
			array_get($result1,'M'),
			array_get($result1,'S')
		);

		$this->baud = array_get($result1,'baud');
		$this->pktver = array_get($result1,'pktver');
		$this->software['prodcode-lo'] = array_get($result1,'prodcode-lo');
		$this->software['prodcode-hi'] = array_get($result2,'prodcode-hi');
		$this->software['rev-maj'] = array_get($result1,'prodrev-maj');
		$this->software['rev-min'] = array_get($result2,'prodrev-min');
		$this->cap['valid'] = array_get($result2,'capvalid');
		$this->cap['word'] = array_get($result2,'capword');
		// @todo filler
	}
}