<?php
// namespace ISO8583;

// use ISO8583\Error\UnpackError;
// use ISO8583\Error\PackError;
require_once(dirname(__FILE__) . '/Error/UnpackError.php');
require_once(dirname(__FILE__) . '/Error/PackError.php');

require_once(dirname(__FILE__) . '/Mapper/AbstractMapper.php');
require_once(dirname(__FILE__) . '/Mapper/AlphaNumeric.php');
require_once(dirname(__FILE__) . '/Mapper/Binary.php');

/**
 * 8583报文
 */
class Message 
{
	protected $protocol;	//8583协议定义
	protected $options;		//配置信息 lengthPrefix appCategory

	protected $length;		//消息长度

    protected $tpdu; 		//TPDU 		ID(60H)+目的地址(N2)+源地址(N2)
    protected $header;		//报文头		{应用类别(N2)}+软件版本号(N2)+终端状态(N1)+处理要求(N1)+保留使用(N6)
    protected $encrypted;	//加密信息	报文长度(A3)+算法标识(A1)+{商户号(A15)}+{终端号(A8)}+交易标识(A10)+响应码(A2)+保留(A2)			
    
    //应用数据 交易数据(ISO8583Msg)
	protected $mti;			//消息类型(Message Type Identifier) —— 0200金融类请求消息
    protected $bitmap;		//位图
    protected $fields = [];	//数据域

	protected $mappers = [	//数据类型
		'a'     => AlphaNumeric::class,
		'n'     => AlphaNumeric::class,
		's'     => AlphaNumeric::class,
		'an'    => AlphaNumeric::class,
		'as'    => AlphaNumeric::class,
		'ns'    => AlphaNumeric::class,
		'ans'   => AlphaNumeric::class,
		'b'	    => Binary::class,
		'z'	    => AlphaNumeric::class
	];

	public function __construct(Protocol $protocol , $options = [])
	{
		$defaults = [
			'lengthPrefix' => null
		];

		$this->options = $options + $defaults;
		$this->protocol = $protocol;
	}

	protected function shrink(&$message, $length) 
	{
		$message = substr($message, $length);
	}

	public function pack()
	{
		// 设置 TPDU
		// $tpdu = $this->tpdu;
		$tpdu = bin2hex($this->tpdu);
		// 设置 报文头
		// $header = $this->header;
		$header = bin2hex($this->header);
		// 设置 加密信息
		// $encrypted = $this->encrypted;
		$encrypted = bin2hex($this->encrypted);
		// 设置 MTI
		// $mti = $this->mti;
		$mti = bin2hex($this->mti);
		
		// Dropping bad fields
		foreach($this->fields as $key=>$val) {
			if (in_array($key, [1, 65])) {
				unset($this->fields[$key]);
			}
		}
		
		// 填充 位图
		$bitmap = "";
		$bitmapLength = 64 * (floor(max(array_keys($this->fields)) / 64) + 1);

		$tmpBitmap = "";
		for($i=1; $i <= $bitmapLength; $i++) {
			if (
				$i == 1 && $bitmapLength > 64 ||
				$i == 65 && $bitmapLength > 128 ||
				isset($this->fields[$i])
			) {
				$tmpBitmap .= '1';
			} else {
				$tmpBitmap .= '0';
			}

			if ($i % 64 == 0) {
				for($i=0; $i<64; $i+=4){
        			$bitmap .= sprintf('%01x', base_convert(substr($tmpBitmap, $i, 4), 2, 10));
      			}
			}
		}
		$this->bitmap = $bitmap;

		// 域排序
		ksort($this->fields);

		// 打包8583报文
		$message = "";
		foreach($this->fields as $id => $data) {
			$fieldData = $this->protocol->getFieldData($id);
			$fieldMapper = $fieldData['type'];

			if (!isset($this->mappers[$fieldMapper])) {
				throw new \Exception('Unknown field mapper for "' . $fieldMapper . '" type');
			}
			
			$mapper = new $this->mappers[$fieldMapper]($fieldData['length']);

			if (
				($mapper->getLength() > strlen($data) && $mapper->getVariableLength() === 0 ) ||
				$mapper->getLength() < strlen($data)
			) {
				$error = 'FIELD [' . $id . '] should have length: ' . $mapper->getLength() . ' and your message "' . $data . "' is " . strlen($data);
				throw new PackError($error);
			}			

			$message .= $mapper->pack($data);		
		}
		
		//加密信息中包含 报文长度
		if (strlen($encrypted) != 0) {
			$encrypted = bin2hex(sprintf('%03d', strlen($message) / 2)) . $encrypted;
		}

		// 打包所有字段 依次为 TPDU、报文头、加密信息、报文体（MTI、位图、域值）
		$message = $mti . $bitmap . $message;

		//首位长度字段
		if ($this->options['lengthPrefix'] > 0) {
			$message = bin2hex(sprintf('%0' . $this->options['lengthPrefix'] . 'd', strlen($message) / 2)) . $message;
		}

		$message = $tpdu . $header . $encrypted . $message;

		//返回打包后的信息
		return $message;
	}

	public function unpack($message)
	{
		// Getting message length if we have one
		if ($this->options['lengthPrefix'] > 0) {
			$length = (int)hex2bin(substr($message, 0, (int)$this->options['lengthPrefix'] * 2));
			$this->shrink($message, (int)$this->options['lengthPrefix'] * 2);

			if (strlen($message) != $length * 2) {
				throw new UnpackError('Message length is ' . strlen($message) / 2 . ' and should be ' . $length);
			}
		}

		// Parsing TPDU 
		$this->setTPDU(hex2bin(substr($message, 0, 20)));
		$this->shrink($message, 20);

		// Parsing Header 
		$this->setHeader(hex2bin(substr($message, 0, 24)));
		$this->shrink($message, 24);

		// Parsing length 
		$this->length = hex2bin(substr($message, 0, 6));

		// Parsing Encrypted 
		$this->setEncrypted(hex2bin(substr($message, 6, 76)));
		$this->shrink($message, 82);

		// Parsing MTI 
		$this->setMTI(hex2bin(substr($message, 0, 8)));
		$this->shrink($message, 8);

		// Parsing bitmap
		$bitmap = "";
		for(;;) {
			$tmp = implode(null, 
				array_map(
					function($bit) {
						return str_pad(base_convert($bit, 16, 2), 8, 0, STR_PAD_LEFT);
					}, 
					str_split(substr($message, 0, 16), 2)
				)
			);

			$this->shrink($message, 16);
			$bitmap .= $tmp;

			if (substr($tmp, 0, 1) !== "1" || strlen($bitmap) > 128) {
				break;
			}
		}

		$this->bitmap = $bitmap;

		if (strlen($message) != $this->length * 2) {
			throw new UnpackError('Message length is ' . strlen($message) / 2 . ' and should be ' . $this->length);
		}

		// Parsing fields
		for($i=0; $i < strlen($bitmap); $i++) {
			if ($bitmap[$i] === "1") {
				$fieldNumber = $i + 1;

				if ($fieldNumber === 1 || $fieldNumber === 65) {
					continue;
				}

				$fieldData = $this->protocol->getFieldData($fieldNumber);
				$fieldMapper = $fieldData['type'];

				if (!isset($this->mappers[$fieldMapper])) {
					throw new \Exception('找不到指定的类型定义：' . $fieldMapper);
				}

				$mapper = new $this->mappers[$fieldMapper]($fieldData['length']);
				$unpacked = $mapper->unpack($message);

				$this->setField($fieldNumber, $unpacked);
			}
		}
	}

	public function getTPDU()
	{
		return $this->tpdu;
	}

	public function setTPDU($tpdu)
	{
		// if (!preg_match('/^60[0-9]{8}$/', $tpdu)) {
		// 	throw new UnpackError('TPDU字段应该是以60开头的10位数字：' . $tpdu);
		// }

		$this->tpdu = $tpdu;
	}

	public function getHeader()
	{
		return $this->header;
	}

	public function setHeader($header)
	{
		// if (!preg_match('/^[0-9]{12}$/', $header)) {	
		// 	throw new UnpackError('Header字段应该是12位数字:' . $header);
		// }

		$this->header = $header;
	}

	//直接获取加密信息中<strong>不</strong>包含报文长度
	public function getEncrypted()
	{
		return $this->encrypted;
	}

	public function setEncrypted($encrypted)
	{
		// if (strlen($encrypted) != 38) {
		//  	throw new UnpackError('Encrypted字段应该是38位字符：' . $encrypted);
		// }

		$this->encrypted = $encrypted;
	}

	public function getMTI()
	{
		return $this->mti;
	}

	public function setMTI($mti)
	{
		// if (!preg_match('/^[0-9]{4}$/', $mti)) {
		// 	throw new UnpackError('MTI字段应该是4位数字！');
		// }

		$this->mti = $mti;
	}

	public function set(array $fields)
	{
		$this->fields = $fields;
	}

	public function getFieldsIds()
	{
		$keys = array_keys($this->fields);
		sort($keys);

		return $keys;
	}

	public function getFields()
	{
		ksort($this->fields);

		return $this->fields;
	}

	public function setField($field, $value)
	{
		$this->fields[(int)$field] = $value;
	}

	public function getField($field)
	{
		return isset($this->fields[$field]) ? $this->fields[$field] : null;
	}

	public function getBitmap()
	{
		return $this->bitmap;
	}
}
