package incheon.res.rdm.com.levylink;

import org.apache.commons.lang3.time.StopWatch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

public class LinkService {
	
	/** Log Info */
    protected Log log = LogFactory.getLog(this.getClass());
    
	private static final byte[] DSD = { 'D', 'S', 'D', 0 };
	private static final byte[] EOD = { 'E', 'O', 'D', 0 };
//	private static final byte[] ACK = { 'A', 'C', 'K', 0 };
//	private static final byte[] NAK = { 'N', 'A', 'K', 0 };
	private static final byte[] OPT = { '0', '5', '0', 0 };
	private static final int CNT = 1;
	public  static final String TYPE = "TYPE";	//전송타입( DSD,EOD,ACK,NAK)
	public  static final String RETURN_DATA = "RETURN_DATA";	//리턴받은 부과키, 오류코드
	
	String sserver = null;
	int pport = 0;
	
    Socket ssocket = null;
	OutputStream oout = null;
	InputStream iin = null;
	
	public LinkService(String server, int port)
	{
	    this.sserver = server;
	    this.pport = port;
	}
	
	
	public Map send(LinkVO lvo) throws LinkException, UnknownHostException,IOException 
	{
		Map map = new HashMap();
    	StopWatch watch = new StopWatch();
    	watch.start();
		
		byte[] type = new byte[4];
		byte[] option = new byte[4];
//		int datacnt = 0;
		int datalen = 0;
		byte[] data = null;

		int len = 0;
		int off = 0;
		byte[] buf = lvo.getData();

		if (DSD == null || OPT == null) {
			return null;
		}

			open();
			oout.write(DSD, 0, DSD.length);
			oout.write(OPT, 0, OPT.length);
			writeInt(CNT);
			writeInt(lvo.getDataLength());
			oout.write(buf, 0, buf.length);
			
			//Receive Type
			len = 0;
			off = 0;
			while (len < 4) 
			{
				off = iin.read(type, len, type.length - len);
				len = len + off;
			}
			log.error("type : "+type);
			map.put(TYPE, new String(type) );
			
			//Receive Option
			len = 0;
			off = 0;
			while (len < 4) 
			{
				off = iin.read(option, len, option.length - len);
				len = len + off;
			}
			log.error("off : "+off +"/ len :"+len);
			//Receive DataCnt
			//datacnt = readInt();
			readInt();
			//log.debug("DataCnt: " + Integer.toString(datacnt));//Debug log
			
			//Receive DataLen
			datalen = readInt();
			log.error("DataLen: " + Integer.toString(datalen));//Debug log			
			
			//Receive Data
			if (datalen > 0) 
			{
				data = new byte[datalen];
				len = 0;
				off = 0;
				while (len < datalen) {
					off = iin.read(data, len, data.length - len);
					len = len + off;
				}
			}
			map.put( RETURN_DATA, new String(data) );
			
			
			//NAK : Error code를 받고 종료
			if (type[0] == 'N' && type[1] == 'A' && type[2] == 'K') 
			{
				// Error Code를 받고, Socket Close
				String msg = new String(data, "UTF-8");
				close();
				log.error("세외수입 부과연계 오류코드 : "+msg);
				//throw new LinkException(msg);//세외수입 부과연계 오류코드
			}
			
			//ACK : EDO를 보내고 + ACK를 받고 종료
			if (type[0] == 'A' && type[1] == 'C' && type[2] == 'K') 
			{
				log.error("세외수입부과연계 결과코드: "+new String(data));
				//ACK는
				// 없음
				// 1. EDO를 보내고
				oout.write(EOD, 0, EOD.length);
				//Send Opt Header
				oout.write(OPT, 0, OPT.length);
				//Send DataCnt Header
				writeInt(CNT);
				//Send DataLen Header
				writeInt(0);

				// 2. ACK를 받고 종료
				//Receive Type
				len = 0;
				off = 0;
				while (len < 4) 
				{
					off = iin.read(type, len, type.length - len);
					len = len + off;
				}
				//Receive Option
				len = 0;
				off = 0;
				
				while (len < 4) 
				{
					off = iin.read(option, len, option.length - len);
					len = len + off;
				}
				//Receive DataCnt
				//datacnt = readInt();
				readInt();
				//Receive DataLen
				datalen = readInt();
				//Receive Data
				data = new byte[datalen];
				if (datalen > 0) 
				{
					len = 0;
					off = 0;
					while (len < 4) {
						off = iin.read(data, len, data.length - len);
						len = len + off;
					}
				}
				// 3. Socket Close
				close();
				// ACK가 아니면 오류
				if (type[0] != 'A' || type[1] != 'C' || type[2] != 'K') 
				{
					String msg = new String(data, "UTF-8");
					log.error("세외수입 부과연계 오류코드 : "+msg);
					throw new LinkException(msg);//세외수입 부과연계 오류코드
				}
			}

		
    	watch.stop();
    	log.error("연계시간:" + watch.getTime());
    	
    	return map;
	}
	
	private void open() throws UnknownHostException, IOException 
	{
		log.error("open : "+sserver+" : " + pport);
		ssocket = new Socket(sserver, pport);
		oout = ssocket.getOutputStream();
		iin = ssocket.getInputStream();
	}
	
	public void close() throws IOException 
	{
		if(oout!=null )oout.close();
		if(iin!=null )iin.close();
		if(ssocket!=null )ssocket.close();		
//		oout = null;
//		iin = null;
//		ssocket = null;
	}
	
	private void writeInt(int i) throws IOException 
	{
		byte[] ibuf = new byte[4];
		//Big-Endian or Network Byte Ordering
		ibuf[3] = (byte) (i & 0x000000FF);
		ibuf[2] = (byte) ((i & 0x0000FF00) >> 8);
		ibuf[1] = (byte) ((i & 0x00FF0000) >> 16);
		ibuf[0] = (byte) ((i & 0xFF000000) >> 24);

		oout.write(ibuf, 0, 4);
	}
	
	private int readInt() throws IOException 
	{
		byte[] ibuf = new byte[4];
		int i = 0;
		int len = 0;
		int off = 0;

		while (len < 4) 
		{
			off = iin.read(ibuf, len, ibuf.length - len);
			
			if (off == -1) {
				break;
			}
			
			len = len + off;
		}
		//Big-Endian or Network Byte Ordering
		i |= (((int) ibuf[0]) << 24) & 0xFF000000;
		i |= (((int) ibuf[1]) << 16) & 0x00FF0000;
		i |= (((int) ibuf[2]) << 8) & 0x0000FF00;
		i |= (((int) ibuf[3])) & 0x0000FF;
		
		return i;
	}

}
