
package org.ajwcc.pduUtils.wappush;

import java.io.*;
import java.text.*;
import java.util.*;
import org.ajwcc.pduUtils.gsm3040.*;

//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
//
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
//PduUtils is distributed under the terms of the Apache License version 2.0
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
public class WapSiUserDataGenerator
{
	//===============================================    
	//  WAP SI User Data generation
	//===============================================    
	//  REFERENCE (orig SMSLib v322 and generated):
	//  1 Visit GMail now! 2 now! now! now! 3 now! now! now! 4 now! now! now! 5 now! now! now! 6 now! now! now! 7 now! now! now! 8 now! now! now! 9 now! now! now!    
	//                                  dcs    udl udhl port            concat           wsp header                 si data        href                                  created               expires            signal      indicationText
	//  PDU1: 0051000C91360936310732 00 F5  FF 8B  0C   05 04 0B84 23F0 08 04 7A5B 02 01 29 06 06 03 AE 81 EA 8D CA 02056A00 45 C6 0E036D61696C2E676F6F676C6500850300                                             08    01 03 3120566973697420474D61696C206E6F77212032206E6F7721206E6F7721206E6F77212033206E6F7721206E6F7721206E6F77212034206E6F7721206E6F7721206E6F77212035206E 6F7721206E6F7721206E6F77212036206E6F
	//        0051000C91360936310732 00 F4  FF 8C  0B   05 04 0B84 23F0 00 03 A8   02 01 01 06 06 03 AE 81 EA 8D CA 02056A00 45 C6 0E036D61696C2E676F6F676C6500850300 0A C30720080523170534 10 C30720080524170534 08    01 03 3120566973697420474D61696C206E6F77212032206E6F7721206E6F7721206E6F77212033206E6F7721206E6F7721206E6F77212034206E6F7721206E6F7721206E6F77212035206E
	//  PDU2: 0051000C91360936310732 00 F5  FF 4F  0C   05 04 0B84 23F0 08 04 7A5B 02 02                                      7721206E6F7721206E6F77212037206E6F7721206E6F7721206E6F77212038206E6F7721206E6F7721206E6F77212039206E6F7721206E6F7721206E6F7721000101
	//        0051000C91360936310732 00 F4  FF 60  0B   05 04 0B84 23F0 00 03 A8   02 02 6F7721206E6F7721206E6F77212036206E6F 7721206E6F7721206E6F77212037206E6F7721206E6F7721206E6F77212038206E6F7721206E6F7721206E6F77212039206E6F7721206E6F7721206E6F7721000101
	private WapSiPdu pdu;

	private ByteArrayOutputStream baos = new ByteArrayOutputStream();

	public void setWapSiPdu(WapSiPdu pdu)
	{
		this.pdu = pdu;
	}

	public byte[] generateWapSiUDBytes()
	{
		try
		{
			baos = new ByteArrayOutputStream();
			// write wsp header
			writeWspHeader();
			// write si data
			writeWapSiData();
			return baos.toByteArray();
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	private void writeWspHeader()
	{
		// WSP header bytes
		// Push Identifier, this can be an arbitrary number??
		// maybe it should be part of the pdu        
		baos.write(0x01);
		// WSP PDU Type = Push
		baos.write(0x06);
		// NOTE: for now leave these hard coded
		//       ideally these should be editable from the WapSiPdu/OutboundWapSIMessage
		// Total header length
		baos.write(0x04);
		// Length of content type (3 bytes)
		baos.write(0x03);
		// value for content-type (0x2E = application/vnd.wap.sic)
		baos.write(0x2E | 0x80);
		// accept-charset header (0x01 = accept-charset)
		baos.write(0x01 | 0x80);
		// accept-charset value (0x6A = utf-8)
		baos.write(WapPushUtils.WBXML_CHARSET_UTF8 | 0x80);
	}

	private void writeWapSiData() throws Exception
	{
		// SI bytes
		// Version of WBXML ( 0x02 = 1.2)
		baos.write(WapPushUtils.WBXML_VERSION_1_2);
		// SI identifier ( 0x05 = SI 1.0)
		baos.write(WapPushUtils.WBXML_SI_1_0_PUBLIC_IDENTIFIER);
		// charset (0x6A = UTF-8)
		baos.write(WapPushUtils.WBXML_CHARSET_UTF8);
		// Table string length (0)
		baos.write(0x00);
		// 0x45 = WBXML coding for tag <SI> with content only (No attributes)
		baos.write(WapPushUtils.WBXML_SI_TAG_CONTENT_NO_ATTRIBUTES);
		// 0xC6 = WBXML coding for tag <indication> with content and attributes
		baos.write(WapPushUtils.WBXML_INDICATION_TAG_CONTENT_AND_ATTRIBUTES);
		// href attribute
		writeHrefAttribute(pdu.getUrl());
		// created attribute
		writeCreatedAttribute(pdu.getCreateDate());
		// expire attribure
		writeExpiresAttribute(pdu.getExpireDate());
		// action attribute
		writeActionAttribute(pdu.getWapSignal());
		// si-id attribute
		writeSiIdAttribute(pdu.getSiId());
		// class attribute
		// writeClassAttribute(baos);
		// WBXML coding for finishing <indication> open tag
		baos.write(WapPushUtils.WBXML_CLOSE_TAG);
		// indication text
		writeText(pdu.getIndicationText());
		// WBXML coding for </si> closing tag
		baos.write(WapPushUtils.WBXML_CLOSE_TAG);
		// WBXML coding for </indication> closing tag
		baos.write(WapPushUtils.WBXML_CLOSE_TAG);
	}

	private void writeHrefAttribute(String url) throws Exception
	{
		// write href attribute        
		if ((url == null) || (url.trim().equals(""))) { throw new RuntimeException("Invalid URL: '" + url + "'"); }
		// scan the start of the href for the protocol bytes
		boolean protocolFound = false;
		for (String protocol : WapPushUtils.getProtocols())
		{
			if (url.startsWith(protocol))
			{
				// write associated byte for this protocol
				baos.write(WapPushUtils.getProtocolByteFor(protocol));
				protocolFound = true;
				url = url.substring(protocol.length());
				break;
			}
		}
		if (protocolFound == false)
		{
			// if no match use 0x0B (unknown)
			baos.write(WapPushUtils.WBXML_HREF_UNKNOWN);
		}
		// write string start
		baos.write(WapPushUtils.WBXML_STRING_START);
		// move one and add character a time and lookahead for the domain strings
		for (int i = 0, lastPosition = 0; i < url.length(); i++)
		{
			for (String domain : WapPushUtils.getDomains())
			{
				// if next characters match the domain
				if (i + domain.length() > url.length())
				{
					// write remainder
					String currentPortion = url.substring(lastPosition, url.length());
					baos.write(currentPortion.getBytes("UTF-8"));
					// make everything end
					i = i + domain.length();
					break;
				}
				if (url.substring(i, i + domain.length()).equalsIgnoreCase(domain))
				{
					// write current string portion of the url
					if (lastPosition < i)
					{
						String currentPortion = url.substring(lastPosition, i);
						baos.write(currentPortion.getBytes("UTF-8"));
					}
					// write domain byte
					baos.write(WapPushUtils.WBXML_STRING_END);
					baos.write(WapPushUtils.getDomainByteFor(domain));
					baos.write(WapPushUtils.WBXML_STRING_START);
					// move index and lastPosition
					i = i + domain.length();
					lastPosition = i;
					// skip to main loop
					break;
				}
			}
		}
		baos.write(WapPushUtils.WBXML_STRING_END);
	}

	private void writeCreatedAttribute(Date createDate) throws Exception
	{
		if (createDate != null)
		{
			// write created indicator
			// write created date info
			baos.write(WapPushUtils.PUSH_CREATED);
			writeDate(createDate);
		}
	}

	private void writeExpiresAttribute(Date expireDate) throws Exception
	{
		if (expireDate != null)
		{
			// write expires indicator
			// write expires date info
			baos.write(WapPushUtils.PUSH_EXPIRES);
			writeDate(expireDate);
		}
	}

	private void writeSiIdAttribute(String siId)
	{
		if ((siId != null) && (siId.trim().equals("")))
		{
			baos.write(WapPushUtils.PUSH_SI_ID);
			writeText(siId);
		}
	}

	//    private void writeClassAttribute(WapSiPdu pdu)
	//    {
	//        // what is this supposed to be?
	//        
	//    }
	private void writeActionAttribute(int wapSignal)
	{
		if (wapSignal != WapPushUtils.PUSH_SIGNAL_MEDIUM)
		{
			// only write if not medium since this is the default if nothing is there
			baos.write(wapSignal);
		}
	}

	//===============================================    
	//  UTILITIES
	//===============================================    
	private void writeText(String text)
	{
		try
		{
			baos.write(WapPushUtils.WBXML_STRING_START);
			// this should depend on the value of the encoding in the WSP header
			// possible values: utf-8, utf-16, ??
			baos.write(text.getBytes("UTF-8"));
			baos.write(WapPushUtils.WBXML_STRING_END);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	private void writeDate(Date date) throws Exception
	{
		// sample 19990625152315 (7 octets) represents date "1999-06-25 15:23:15"
		// sample 20990603       (4 octets) represents date "2099-06-30 00:00:00"
		SimpleDateFormat sdf = new SimpleDateFormat();
		sdf.applyPattern("yyyyMMddHHmmss");
		String dateData = sdf.format(date);
		// scan from the last octet to start and remove all trailing 00s
		for (int i = 6; i >= 0; i--)
		{
			if (dateData.endsWith("00"))
			{
				dateData = dateData.substring(0, i * 2);
			}
			else
			{
				break;
			}
		}
		// generate the byte[] from remaining date data
		byte[] dataBytes = PduUtils.pduToBytes(dateData);
		// mark opaque data
		baos.write(WapPushUtils.WBXML_OPAQUE_DATA);
		// write octet length
		baos.write(dataBytes.length);
		// write data
		baos.write(dataBytes);
	}
}
