package com.szpg.plc.protocol.fins.frame;

import org.apache.log4j.Logger;

import com.szpg.plc.protocol.fins.FINSConstants;
import com.szpg.plc.util.ByteUtil;
import com.szpg.plc.util.Bytes;

/**
 * FINS协议的帧结构
 * 暂时用到的有握手命令和读取命令两大类
 * 
 * 两种命令都包括命令（从客户端到服务端）以及命令的响应（从服务端到客户端）。其中，PC端表示客户端，PLC端表示服务端
 * 1握手命令帧结构
 *       46494E53 0000000C 00000000 00000000 000000C7
 * 含义             FINS包头        数据长度             功能码               错误码            本机地址
 * 
 * 2握手命令响应帧结构
 *       46494E53 00000010 00000001 00000000 000000C7 00000001
 * 含义             FINS包头        数据长度             功能码               错误码            本机地址            PLC地址
 * 
 * 3读取命令帧结构
 *       46494E53 0000001A 00000002 00000000 800002 000100 00C700 00  0101 82   000000  0002
 * 含义             FINS包头        数据长度             功能码               错误码            ICF/   DNA/   SNA/  SID  读取    DM区       起始地址      读取数(WORD)
 *                                            RSV/   DA1/   SA1/
 *                                            GCT    DA2    SA2
 * 
 * 4读取命令响应帧结构
 *       46494E53 0000001A 00000002 00000000 C00002 00C700 000100 00  0101   0000        C0004541
 * 含义             FINS包头        数据长度             功能码               错误码            ICF/   DNA/   SNA/  SID  读取     END CODE/   两个字(WORD)数据
 *                                            RSV/   DA1/   SA1/            正常读取
 *                                            GCT    DA2    SA2
 * 
 * 5写命令帧结构
 *       46494E53 0000001E 00000002 00000000 800002 000100 00C700 00  0102 82  000000  0002        C0004541
 * 含义             FINS包头        数据长度             功能码               错误码            ICF/   DNA/   SNA/  SID  写      DM区       起始地址      写入数(WORD)   写入的数据
 *                                            RSV/   DA1/   SA1/
 *                                            GCT    DA2    SA2
 * 
 * 6写命令响应帧结构
 *       46494E53 00000016 00000002 00000000 C00002 00C700 000100 00  0102   0000
 * 含义             FINS包头        数据长度             功能码               错误码            ICF/   DNA/   SNA/  SID  写入     END CODE/
 *                                            RSV/   DA1/   SA1/            写入正常
 *                                            GCT    DA2    SA2
 * 
 * @author admin
 *
 */
public class FINSByteFrame {
	
	/**************** 帧头部数据区 ****************/

	// 起始字符FINS的ASCII码, 四字节
	public static byte[] HEADER = new byte[] {0x46, 0x49, 0x4E, 0x53};

	// 用户数据区长度字符, 四字节
	public byte[] LENGTH = new byte[4];
	
	// 功能码，四字节
	public byte[] COMMAND = new byte[4];
	
	// 错误码，四字节
	public byte[] ERROR_CODE = new byte[4];
	
	
	/**************** 用户数据区DATA ****************/
	public byte[] TEXT_CONTROL_BODY;
	public byte[] TEXT_DATA_BODY;
	
	/**************** 控制域数据区 ****************/
	public byte TEXT_CONTROL_ICF;
	public byte TEXT_CONTROL_RSV = FINSConstants.RSV;
	public byte TEXT_CONTROL_GCT = FINSConstants.GCT;
	public byte TEXT_CONTROL_DNA;
	public byte TEXT_CONTROL_DA1;
	public byte TEXT_CONTROL_DA2;
	public byte TEXT_CONTROL_SNA;
	public byte TEXT_CONTROL_SA1;
	public byte TEXT_CONTROL_SA2;
	public byte TEXT_CONTROL_SID;
	
	public byte[] FINS_COMMAND_CODE = new byte[2];
	public byte MEMORY_AREA_CODE;
	public byte[] BEGIN_ADDRESS = new byte[3];
	public byte[] NO_ITEMS = new byte[2];
	
	// 用于上行响应帧
	public boolean valid;
	
	private final Logger logger = Logger.getLogger(this.getClass().getName());

	/**
	 * 构造方法 由帧字节解析为帧对象
	 * 用于验证从PLC返回的消息
	 * 
	 * @param byteMessage
	 */
	public FINSByteFrame(byte[] byteMessage) {
		if (checkHearder(byteMessage) == false) {
			this.valid = false;
			logger.debug("帧头不是FINS" + ByteUtil.binToHexString(byteMessage, 0, 8));
		} else if (checkLength(byteMessage) == false) {
			this.valid = false;
			logger.debug("帧长度不符" + ByteUtil.binToHexString(byteMessage, 8, 16));
		} else if (byteMessage.length > 26 && checkControlICF(byteMessage) == false) {
			this.valid = false;
			logger.debug("帧ICF字节不符" + ByteUtil.binToHexString(new byte[] {byteMessage[16]}));
		} else if (checkTextData(byteMessage) == false) {
			this.valid = false;
			logger.debug("帧用户数据域不符" + ByteUtil.binToHexString(byteMessage, 16, byteMessage.length * 2));
		} else {
			this.valid = true;
		}
	}
	
	/**
	 * 构造方法 DATA = LOCALHOST.NODE
	 * 适用于上位机发送握手命令
	 * 
	 * @param sNode 本机IP地址的末字节
	 */
	public FINSByteFrame(String sNode) {
		this.TEXT_CONTROL_BODY = null; // 控制域为空

		// 用户数据域为本机地址的末位
		this.TEXT_DATA_BODY = ByteUtil.hexStringToBytes(sNode, 4);

		// 计算帧长度并赋值
		this.LENGTH = ByteUtil.intToBins(length(), 4);
	}

	/**
	 * 构造方法
	 * 适用于读取内存命令
	 * 
	 * @param dest 目标地址
	 * @param sour 源地址
	 * @param sid 服务号
	 * @param memArea 内存区域代码
	 * @param start 读取的起始地址
	 * @param count 读取的长度(以word计，长度为2个字节)
	 */
	public FINSByteFrame(String dest, String sour, String sid, byte memArea, byte[] start, int count) {
		this.TEXT_CONTROL_BODY = FINSByteFrameTool.buildFrameControl(dest, sour, sid); // 构造控制域
		
		// 设置读取内存命令的发送命令功能码00000002
		this.COMMAND = FINSConstants.COMMAND_COMMAND;
		
		// 设置错误码为00000000
		this.ERROR_CODE = FINSConstants.ERROR_NORMAL;
		
		// 构造用户数据域的内容
		Bytes data = new Bytes();
		data.append(FINSConstants.COMMAND_MEMORY_READ);
		data.append(memArea);
		data.append(start);
		data.append(ByteUtil.intToBins(count, 2));
		
		// 为用户数据域赋值
		this.TEXT_DATA_BODY = data.toBytes();

		// 计算帧长度并赋值
		this.LENGTH = ByteUtil.intToBins(length(), 4);
	}

	/**
	 * 构造方法
	 * 适用于写内存命令
	 * 
	 * @param dest
	 * @param sour
	 * @param sid
	 * @param memArea
	 * @param start
	 * @param count
	 * @param dataBody
	 */
	public FINSByteFrame(String dest, String sour, String sid, byte memArea, byte[] start, int count, byte[] dataBody) {
		this.TEXT_CONTROL_BODY = FINSByteFrameTool.buildFrameControl(dest, sour, sid); // 构造控制域

		// 构造用户数据域的内容
		Bytes data = new Bytes();
		data.append(FINSConstants.COMMAND_MEMORY_WRITE);
		data.append(memArea);
		data.append(start);
		data.append(ByteUtil.intToBins(count, 2));
		data.append(dataBody);

		// 为用户数据域赋值
		this.TEXT_DATA_BODY = data.toBytes();

		// 计算帧长度并赋值
		this.LENGTH = ByteUtil.intToBins(length(), 4);
	}
	
	
	/**
	 * 判断帧起始字符
	 * 
	 * @param byteMessage
	 * @return
	 */
	private boolean checkHearder(byte[] byteMessage) {
		if (byteMessage.length < 4) {
			return false;
		}
		
		if (byteMessage[0] == FINSByteFrame.HEADER[0] && 
			byteMessage[1] == FINSByteFrame.HEADER[1] && 
			byteMessage[2] == FINSByteFrame.HEADER[2] && 
			byteMessage[3] == FINSByteFrame.HEADER[3])
			return true;
		else
			return false;
	}
	
	/**
	 * 判断帧长度
	 * 
	 * @param byteMessage
	 * @return
	 */
	private boolean checkLength(byte[] byteMessage) {
		if (byteMessage.length < 8) {
			return false;
		}
		
		if (byteMessage.length == ByteUtil.binToInt(FINSByteFrameTool.getFrameLength(byteMessage)) + 8) {
			this.LENGTH = FINSByteFrameTool.getFrameLength(byteMessage);
			return true;
		} else {
			return false;
		}
	}

	/**
	 * 判断ICF字节
	 * 从PLC端返回的消息该字节D7-D6为1
	 * 
	 * @param byteMessage
	 * @return
	 */
	private boolean checkControlICF(byte[] byteMessage) {
		byte icf = byteMessage[16];
		
		if ((icf & 0xC0) == 0xC0) {
			Bytes ctrlBytes = new Bytes();
			for (int i = 16; i < 26; i++) {
				ctrlBytes.append(byteMessage[i]);
			}
			
			// 为控制域赋值
			this.TEXT_CONTROL_BODY = ctrlBytes.toBytes();
			this.TEXT_CONTROL_ICF = this.TEXT_CONTROL_BODY[0];
			this.TEXT_CONTROL_RSV = FINSConstants.RSV;
			this.TEXT_CONTROL_GCT = FINSConstants.GCT;
			this.TEXT_CONTROL_DNA = this.TEXT_CONTROL_BODY[3];
			this.TEXT_CONTROL_DA1 = this.TEXT_CONTROL_BODY[4];
			this.TEXT_CONTROL_DA2 = this.TEXT_CONTROL_BODY[5];
			this.TEXT_CONTROL_SNA = this.TEXT_CONTROL_BODY[6];
			this.TEXT_CONTROL_SA1 = this.TEXT_CONTROL_BODY[7];
			this.TEXT_CONTROL_SA2 = this.TEXT_CONTROL_BODY[8];
			this.TEXT_CONTROL_SID = this.TEXT_CONTROL_BODY[9];
			
			return true;
		}
		else 
			return false;
	}
	
	private boolean checkTextData(byte[] byteMessage) {
		Bytes dataBytes = new Bytes();
		if (byteMessage.length < 26) {
			// 握手命令响应，数据域从第16个字节开始
			for (int i = 16; i < byteMessage.length; i++) {
				dataBytes.append(byteMessage[i]);
			}
		} else {
			// 读取命令，数据域从第26个字节开始
			for (int i = 26; i < byteMessage.length; i++) {
				dataBytes.append(byteMessage[i]);
			}
		}
		
		// 为数据域赋值
		this.TEXT_DATA_BODY = dataBytes.toBytes();
		
		return true;
	}

	/**
	 * 计算帧长度
	 * 
	 * @return
	 */
	private int length() {
		int len = 0;
		
		// 用控制域是否有内容来判断是握手命令还是读写命令
		if (null == this.TEXT_CONTROL_BODY) {
			len = 4 + // 功能码长度
				  4 + // 错误码长度
				  4; //本机地址的末位
		} else {
			len = 4 + // 功能码长度
				  4 + // 错误码长度
				  this.TEXT_CONTROL_BODY.length + // 控制域长度[10个字节]
				  this.TEXT_DATA_BODY.length; // 用户数据域长度
		}

		return len;
	}

	/**
	 * 生成标准字节消息
	 * 
	 * @return
	 */
	public byte[] toBytes() {
		byte[] frame = null;

		Bytes bytes = new Bytes();
		bytes.append(FINSByteFrame.HEADER) // 帧起始字符
			 .append(this.LENGTH) // 帧用户数据区长度字节
			 .append(this.COMMAND) // 命令码
			 .append(this.ERROR_CODE) // 错误码
			 .append(this.TEXT_CONTROL_BODY) //控制域数据区
			 .append(this.TEXT_DATA_BODY); // 用户区数据

		frame = bytes.toBytes();

		// 最后判断帧长度是否正确
		if (frame.length == ByteUtil.binToInt(this.LENGTH) + 8) {
			return frame;
		} else {
			return null;
		}
	}
}
