/*
 *    
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package com.sun.tck.wma.sms;

import com.sun.tck.wma.PropLoader;
import com.sun.tck.wma.BinaryMessage;
import com.sun.tck.wma.Message;
import com.sun.tck.wma.MessageConnection;
import com.sun.tck.wma.MessageTransportConstants;
import com.sun.tck.wma.TextMessage;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Properties;
import com.sun.midp.io.j2me.sms.TextEncoder;

import java.io.IOException;


/**
 * SMS message connection handler.
 */
public class SMSMessageConnection extends PropLoader 
    implements MessageConnection {

    /** Machine name - the parsed target address from the URL. */
    protected String host = null;

    /** Port number from the URL connection string. */
    protected String port = null;

    /** Datagram host for sending/receiving. */
    protected String clientHost;

    /** Datagram transport for sending. */
    protected int portOut;

    /** Datagram transport for receiving. */
    protected int portIn;

    /** Phone number of the message sender. */
    protected String phoneNumber;

    /** Fragment size for large messages. */
    int fragmentsize;

    /** Datagram server connection. */
    DatagramSocket dgc; 

    /** Datagram buffer. */
    byte [] buf = new byte[MessageTransportConstants.DATAGRAM_PACKET_SIZE];

    /** Datagram envelope for sending or receiving messages. */
    DatagramPacket mess =
        new DatagramPacket(buf, MessageTransportConstants.DATAGRAM_PACKET_SIZE);

    /**
     * Open flag indicates when the connection is closed. When a connection is
     * closed, subsequent operations throw an exception.
     */
    protected boolean open;

    /** Constructor for SMS message connection handling. */
    public SMSMessageConnection() {

        /* 
         * Configurable parameters for low level transport.
         * e.g. sms://+5551234:54321 maps to datagram://129.148.70.80:123
         */

        clientHost = "localhost";
        portOut = 11100;
        portIn = 11101;
        phoneNumber = "+5551234";

        /* 
         * Check for overrides in the "connections.prop"
         * configuration file.
         */

        clientHost = getProp("localhost", "JSR_205_DATAGRAM_HOST",
            "connections.prop", "DatagramHost");
        
        portOut = getIntProp(11100, "JSR_205_SMS_OUT_PORT",
            "connections.prop", "SMSDatagramPortOut");

        portIn = getIntProp(11101, "JSR_205_SMS_PORT",
            "connections.prop", "SMSDatagramPortIn");

        phoneNumber = getProp("+5551234", "JSR_205_PHONE_NUMBER",
            "connections.prop", "PhoneNumber");
                
    }

    /**
     * Opens a connection.
     * <p>
     * This method is called from <code>Connector.open()</code> method to obtain
     * the destination address given in the <code>name</code> parameter.
     * <p>
     * The format for the <code>name</code> string for this method is:
     * <code>sms://<em>phone_number</em>:<em>port</em></code>
     * where the <em>phone_number:</em> is optional. If the
     * <em>phone_number</em> parameter is present, the connection is being
     * opened in "client" mode. This means that messages can be sent. If the
     * parameter is absent, the connection is being opened in "server" mode.
     * This means that messages can be sent and received.
     * <p>
     * The connection that is opened is to a low-level transport mechanism which
     * can be any of the following:
     * <ul>
     * <li>A datagram Short Message Peer to Peer (SMPP) to a service 
     * center.
     * <li>A <code>comm</code> connection to a phone device with AT-commands.
     * </ul>
     *
     * @param name the target of the connection
     * @return this connection
     * @throws IOException if the connection is closed or unavailable.
     */
    public MessageConnection openPrim(String name)
        throws IOException {
        /*
         * If <code>host == null</code>, then we are a server endpoint at
         * the supplied <code>port</code>.
         *
         * If <code>host != null</code> we are a client endpoint at a port
         * decided by the system and the default address for
         * SMS messages to be sent is <code>sms://host:port</code> .
         */
        
        if (name.charAt(0) != '/' || name.charAt(1) != '/') {
            throw new IllegalArgumentException(
                           "Missing protocol separator.");
        }
        
        int colon = name.indexOf(':');
        if (colon > 0) {
            if (colon != 2) {
                host = name.substring(2, colon);
            }
            port = name.substring(colon + 1);
        } else {
            if (name.length() > 2) {
                host = name.substring(2); 
            }
        }

        /* Open the inbound server datagram connection. */
        dgc =  new DatagramSocket(portIn);
        
        open = true;
        return this;
    }

    /**
     * Constructs a new message object of a text or binary type.
     * <p>
     * When the <code>TEXT_MESSAGE</code> constant is passed in, the created
     * object implements the <code>TextMessage</code> interface. When the
     * <code>BINARY_MESSAGE</code> constant is passed in, the created object
     * implements the <code>BinaryMessage</code> interface. 
     * <p>
     * If this method is called in a sending mode, a new <code>Message</code>
     * object is requested from the connection. For example:
     * <p>
     * <code>Message msg = conn.newMessage(TEXT_MESSAGE);</code>
     * <p>
     * The newly created <code>Message</code> does not have the destination
     * address set. It must be set by the application before the message is
     * sent.
     * <p>
     * If it is called in receiving mode, the <code>Message</code> object does
     * have its address set. The application can act on the object to extract
     * the address and message data. 
     * <p>
     * <!-- The <code>type</code> parameter indicates the number of bytes 
     * that should be
     * allocated for the message. No restrictions are placed on the application 
     * for the value of <code>size</code>.
     * A value of <code>null</code> is permitted and creates a 
     * <code>Message</code> object 
     * with a 0-length message. -->
     * 
     * @param  type either TEXT_MESSAGE or BINARY_MESSAGE.
     * @return      a new message
     */
    public Message newMessage(String type) {
        return newMessage(type, null);
    }

    /**
     * Constructs a new message object of a text or binary type and specifies
     * a destination address.
     * When the 
     * <code>TEXT_MESSAGE</code> constant is passed in, the created 
     * object implements the <code>TextMessage</code> interface.
     * When the <code>BINARY_MESSAGE</code> constant is passed in, the 
     * created object implements the <code>BinaryMessage</code> 
     * interface. 
     * <p>
     * The destination address <code>addr</code> has the following format:
     * <code>sms://</code><em>phone_number</em>:<em>port</em>.
     *
     * @param  type either TEXT_MESSAGE or BINARY_MESSAGE.
     * @param  addr the destination address of the message.
     * @return      a new <code>Message</code> object.
     */
    public Message newMessage(String type, String addr)  {
        /* Return the appropriate type of sub message. */
        if (type == MessageConnection.TEXT_MESSAGE) {
            return  new TextObject(addr); 
        } else if (type == MessageConnection.BINARY_MESSAGE) {
            return  new BinaryObject(addr);
        }
        return null; /* message type not supported */
    }

    /**
     * Sends a message over the connection. This method extracts
     * the data payload from 
     * the <code>Message</code> object so that it
     * can be sent as a datagram. 
     *
     * @param     dmsg a <code>Message</code> object.
     * @exception ConnectionNotFoundException  if the address is 
     *    invalid or if no address is found in the message.
     * @exception IOException  if an I/O error occurs.
     */
    public void send(Message dmsg) throws IOException {

        /** Saved timestamp for use with multiple segment records. */
        long sendtime = System.currentTimeMillis();

        byte[] buffer = null;
        String type = null;
        if (dmsg instanceof TextMessage) {
            type = MessageConnection.TEXT_MESSAGE;
            buffer = ((TextObject)dmsg).getPayloadData();
        } else if (dmsg instanceof BinaryMessage) {
            type = MessageConnection.BINARY_MESSAGE;
            buffer = ((BinaryObject)dmsg).getPayloadData();
        }

        /*
         * For text messages choose between UCS-2 or GSM 7-bit
         * encoding.
         */
        int encodingType = MessageTransportConstants.GSM_BINARY;
        if (type.equals(MessageConnection.TEXT_MESSAGE)) {
            byte[] gsm7bytes = TextEncoder.encode(buffer);
            if (gsm7bytes != null) {
                encodingType = MessageTransportConstants.GSM_TEXT;
                buffer = gsm7bytes;
            } else {
                encodingType = MessageTransportConstants.GSM_UCS2;
            }
        }


        mess = new DatagramPacket(buf,
                   MessageTransportConstants.DATAGRAM_PACKET_SIZE);
        mess.setAddress(InetAddress.getByName(clientHost));
        mess.setPort(portOut);

        SMSPacket smsPacket = new SMSPacket();
        smsPacket.setEncodingType(encodingType);
	if (port != null) {
            smsPacket.setPort(Integer.parseInt(port));
	} else {
            smsPacket.setPort(0);
	}
        smsPacket.setTimeStamp(sendtime);

        /*
         * Set target address.
         */
        smsPacket.setAddress(dmsg.getAddress());

        /*
         * Set sender's phone number
         */
        smsPacket.setPhoneNumber(phoneNumber);

        smsPacket.setMessageLength(buffer.length);
        smsPacket.setMessage(buffer);

        debug("SMS PACKET: encoding type = " + encodingType);
        debug("SMS PACKET: port = " + port);
        debug("SMS PACKET: timestamp = " + sendtime);
        debug("SMS PACKET: address = " + dmsg.getAddress());
        debug("SMS PACKET: sender's phone number = " + phoneNumber);
        debug("SMS PACKET: message length = " + buffer.length);
        debug("SMS PACKET: message:" + new String(buffer));
        byte[] buf = smsPacket.getData();

        mess.setData(buf, 0, buf.length);
        dgc.send(mess);
    }
    
    /**
     * Receives the bytes that have been sent over the connection, constructs a
     * <code>Message</code> object, and returns it. 
     * <p>
     * If there are no <code>Message</code>s waiting on the connection, this
     * method will block until a message is received, or the
     * <code>MessageConnection</code> is closed.
     *
     * @return a <code>Message</code> object
     * @exception IOException  if an I/O error occurs.
     */
    public synchronized Message receive()
        throws IOException {

        dgc.receive(mess);

        /* get message buffer and extract info */
        byte[] buf = mess.getData();
        SMSPacket packet = new SMSPacket(buf);
        int msgType = packet.getEncodingType();
        int smsPort = packet.getPort();
        long time = packet.getTimeStamp();
        String address = packet.getAddress();
        String phoneNumber = packet.getPhoneNumber();
        int msgLen = packet.getMessageLength();
        byte[] messg = packet.getMessage(msgLen);

        debug("SMS PACKET: encodingType = " + msgType);
        debug("SMS PACKET: port = " + smsPort);
        debug("SMS PACKET: time = " + time);
        debug("SMS PACKET: address = " + address);
        debug("SMS PACKET: Sender's phone number = " + phoneNumber);
        debug("SMS PACKET: message length = " + msgLen);
        debug("SMS PACKET: message:" + new String(messg));

        Message msg = null;
        if (msgType == MessageTransportConstants.GSM_TEXT ||
            msgType == MessageTransportConstants.GSM_UCS2) {

            TextMessage textmsg = (TextMessage) 
                newMessage(MessageConnection.TEXT_MESSAGE,
                           address);

            /* Always store text messages as UCS 2 bytes. */
            if (msgType == MessageTransportConstants.GSM_TEXT) {
                messg = TextEncoder.decode(messg);
            }
            ((TextObject)textmsg).setPayloadData(messg);
             msg = textmsg;
         } else {
             BinaryMessage binmsg = (BinaryMessage) 
                 newMessage(MessageConnection.BINARY_MESSAGE,
                            address);
             ((BinaryObject)binmsg).setPayloadData(messg);
             msg = binmsg;
         }
         ((MessageObject) msg).setTimeStamp(time);

        return msg; 
    }


    /**
     * Closes the connection. Reset the connection is open flag
     * so methods can be checked to throws an appropriate exception
     * for operations on a closed connection.
     *
     * @exception IOException  if an I/O error occurs.
     */
    public void close() throws IOException {

        if (open) {
            dgc.close();
            dgc = null;
            open = false;
        }
    }

    /**
     * Show a debug message.
     *
     * @param text  The text to be displayed after a mandatory prefix.
     */
    private void debug(String text) {
        System.out.println("SMSMessageConnection: " + text);
    }

}
