/*
 * ICESOFT COMMERCIAL SOURCE CODE LICENSE V 1.1
 *
 * The contents of this file are subject to the ICEsoft Commercial Source
 * Code License Agreement V1.1 (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.icesoft.com/license/commercial-source-v1.1.html
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations under
 * the License.
 *
 * Copyright 2009-2015 ICEsoft Technologies Canada, Corp. All Rights Reserved.
 */

package com.icesoft.icepush;

//import com.twilio.sdk.TwilioRestClient;
//import com.twilio.sdk.TwilioRestException;
//import com.twilio.sdk.resource.factory.SmsFactory;
//import com.twilio.sdk.resource.instance.Account;
//import com.twilio.sdk.resource.instance.Sms;

import com.icesoft.icepush.util.Base64;
import org.icepush.NotificationProvider;
import org.icepush.OutOfBandNotifier;
import org.icepush.PushNotification;
import org.icepush.util.ExtensionRegistry;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.logging.Level;
import java.util.logging.Logger;

//import java.util.HashMap;
//import java.util.Map;

/**
 * An implementation of a NotificationProvider that uses Twilio's SMS gateway service.
 */
public class TwilioSMSNotificationProvider implements NotificationProvider {

    private static final Logger log = Logger.getLogger(TwilioSMSNotificationProvider.class.getName());

    //The key to use if the credentials are specified using ICEfaces system property or parameter
    private static final String TWILIO_CREDENTIALS_KEY = "org.icemobile.twilio.credentials";

    //We typically jam the two parts of the credentials together using this as the delimiter
    public static final String CREDENTIALS_DELIMITER = ":";

    private OutOfBandNotifier outOfBandNotifier;

    //The credentials for using Twilio
    private String accountID;
    private String authToken;

    //The phone number to send SMS from (e.g. "(510) 555-1212")
    private String sendingNumber;

    //A URL to include in the message that links back to the notifying app.  For now
    //we'll just use a context parameter as this is designed to be temporary until
    //we have a solution that works for all SMS situations.
    private static final String TWILIO_SEND_URL_KEY = "org.icemobile.twilio.sendURL";
    private String sendURL;

    //A URL to include in the message that links back to the notifying app.  For now
    //we'll just use a context parameter as this is designed to be temporary until
    //we have a solution that works for all SMS situations.
    private static final String TWILIO_RETURN_LINK_KEY = "org.icemobile.twilio.returnLink";
    private String returnLink;

    public TwilioSMSNotificationProvider() {
    }

    public void send(String uri, PushNotification notification) {
        sendViaRest(uri, notification);
//        sendViaClient(uri,notification);
    }

    /**
     * Using this method requires that the Twilio client library and all its
     * dependencies be available on the classpath.  It is commented out to
     * avoid those compile-time and runtime dependencies.
     *
     * @param uri
     * @param notification
     */
//    public void sendViaClient(String uri, PushNotification notification){
//         //Parse out the phone number
//        String smsNumber = uri.substring("sms:".length());
//        log.log(Level.FINE, "preparing to send " +
//                "\n  from   : " + sendingNumber +
//                "\n  to     : " + smsNumber +
//                "\n  subject: " + notification.getSubject() +
//                "\n  detail : " + notification.getDetail() +
//                "\n  return : " + returnLink
//        );
//
//        final TwilioRestClient client = new TwilioRestClient(accountID, authToken);
//
//        final Account mainAccount = client.getAccount();
//
//        //This example was provided directly from Twilio's "how to" examples
//        final SmsFactory smsFactory = mainAccount.getSmsFactory();
//
//        final Map<String, String> smsParams = new HashMap<String, String>();
//        smsParams.put("To", smsNumber);
//        smsParams.put("From", sendingNumber);
//        smsParams.put("Body", composeMessage(notification));
//
//        try {
//            Sms result = smsFactory.create(smsParams);
//            log.log(Level.FINE, "SMS result: " + result);
//        } catch (TwilioRestException e) {
//            log.log(Level.WARNING, "problem sending message", e);
//            e.printStackTrace();
//        }
//    }
    public void sendViaRest(String uri, PushNotification notification) {

        //Parse out the phone number
        String smsNumber = uri.substring("sms:".length());
        log.log(Level.FINE, "preparing to send " +
                "\n  url    : " + sendURL +
                "\n  from   : " + sendingNumber +
                "\n  to     : " + smsNumber +
                "\n  subject: " + notification.getSubject() +
                "\n  detail : " + notification.getDetail() +
                "\n  return : " + returnLink
        );

        HttpURLConnection httpConn = null;
        try {
            URL smsURL = new URL(sendURL);

            httpConn = (HttpURLConnection) smsURL.openConnection();
            httpConn.setRequestMethod("POST");
            httpConn.setDoOutput(true);
            httpConn.setReadTimeout(5000);
            httpConn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");

            String encodedAuth = Base64.encode((accountID + ":" + authToken));
            httpConn.setRequestProperty("Authorization", "Basic " + encodedAuth);

            StringBuilder params = new StringBuilder();
            params.append("To=").append(URLEncoder.encode(smsNumber, "UTF-8")).append("&");
            params.append("From=").append(URLEncoder.encode(sendingNumber, "UTF-8")).append("&");
            params.append("Body=").append(URLEncoder.encode(composeMessage(notification), "UTF-8"));

            log.log(Level.FINE, "params: " + params.toString());

            httpConn.connect();

            DataOutputStream wr = new DataOutputStream(httpConn.getOutputStream());
            wr.writeBytes(params.toString());
            wr.flush();
            wr.close();

            StringBuilder buff = new StringBuilder(" -- RESPONSE -- [");
            buff.append(httpConn.getResponseCode()).append("  ").append(httpConn.getResponseMessage()).append("\n");

            BufferedReader in = new BufferedReader(new InputStreamReader(httpConn.getInputStream()));
            String currentLine = null;
            while ((currentLine = in.readLine()) != null) {
                buff.append(currentLine + '\n');
            }

            log.log(Level.FINE, "response:\n" + buff.toString());

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (httpConn != null) {
                httpConn.disconnect();
            }
        }

    }


    private String composeMessage(PushNotification notification) {
        String messageBody = notification.getDetail();
        if (messageBody == null || messageBody.trim().length() == 0) {
            messageBody = notification.getSubject();
            if (messageBody == null || messageBody.trim().length() == 0) {
                messageBody = "ICEnotify:  This is a notification from a web application.";
            }
        }

        if (returnLink != null) {
            messageBody = messageBody + " " + returnLink;
        }
        return messageBody;
    }

    public void registerWith(OutOfBandNotifier outOfBandNotifier) {
        outOfBandNotifier.registerProvider("sms", this);
        this.outOfBandNotifier = outOfBandNotifier;
    }

    public boolean establishCredentials(String defaultValue) {

        //Try for a system property first
        String systemPropertyValue = System.getProperty(TWILIO_CREDENTIALS_KEY);

        String[] credentials = parseCredentials(systemPropertyValue);
        if (credentials != null) {
            accountID = credentials[0];
            authToken = credentials[1];
            return true;
        }

        credentials = parseCredentials(defaultValue);
        if (credentials != null) {
            accountID = credentials[0];
            authToken = credentials[1];
            return true;
        }

        log.log(Level.FINE, "no valid credentials");
        return false;
    }

    private static String[] parseCredentials(String credentialData) {
        if (credentialData == null) {
            return null;
        }

        int delimiterIndex = credentialData.indexOf(CREDENTIALS_DELIMITER);
        if (delimiterIndex < 1) {
            return null;
        }

        String[] credentials = new String[2];
        credentials[0] = credentialData.substring(0, delimiterIndex);
        credentials[1] = credentialData.substring(delimiterIndex + 1);

        return credentials;
    }

    public String getSendURL() {
        return sendURL;
    }

    public void setSendURL(String sendURL) {
        this.sendURL = sendURL;
    }

    public String getSendingNumber() {
        return sendingNumber;
    }

    public void setSendingNumber(String sendingNumber) {
        this.sendingNumber = sendingNumber;
    }

    public String getReturnLink() {
        return returnLink;
    }

    public void setReturnLink(String returnLink) {
        this.returnLink = returnLink;
    }

    public static class AutoRegister implements ServletContextListener {

        //Private logger required to avoid instantiating the outer class and to help with NoClassDefFoundError
        private static final Logger registerLog = Logger.getLogger(AutoRegister.class.getName());

        private static final String TWILIO_SEND_URL_KEY = "org.icemobile.twilio.sendURL";
        private static final String TWILIO_SEND_URL_DEFAULT_PREFIX = "https://api.twilio.com/2010-04-01/Accounts/";
        private static final String TWILIO_SEND_URL_DEFAULT_SUFFIX = "/SMS/Messages";

        private static final String TWILIO_SENDING_NUMBER_KEY = "org.icemobile.twilio.sendingNumber";
        private static final String TWILIO_SENDING_NUMBER_DEFAULT = "5145000423";

        public void contextInitialized(ServletContextEvent servletContextEvent) {

            try {
                //Create a new provider
                TwilioSMSNotificationProvider provider = new TwilioSMSNotificationProvider();

                ServletContext servletContext = servletContextEvent.getServletContext();
                boolean credentialsEstablished =
                        provider.establishCredentials(servletContext.getInitParameter(TWILIO_CREDENTIALS_KEY));

                if (!credentialsEstablished) {
                    throw new IllegalStateException("could not establish credentials");
                }

                //This is the REST-style URL used to send SMS messages via Twilio
                String param = servletContext.getInitParameter(TWILIO_SEND_URL_KEY);
                String defaultURL = TWILIO_SEND_URL_DEFAULT_PREFIX + provider.accountID + TWILIO_SEND_URL_DEFAULT_SUFFIX;
                provider.setSendURL(param != null ? param : defaultURL);

                //This is the phone number the SMS message is sent from
                param = servletContext.getInitParameter(TWILIO_SENDING_NUMBER_KEY);
                provider.setSendingNumber(param != null ? param : TWILIO_SENDING_NUMBER_DEFAULT);

                param = servletContext.getInitParameter(TWILIO_RETURN_LINK_KEY);
                if (param != null) {
                    provider.setReturnLink(param);
                }

                ExtensionRegistry.addExtension(servletContext, 10, NotificationProvider.class.getName(), provider);
                registerLog.log(Level.INFO, "Twilio SMS Notification Provider is on: " + provider.getSendingNumber());

            } catch (Throwable t) {
                registerLog.log(Level.INFO, "Twilio SMS Notification Provider is off");
                registerLog.log(Level.FINE, "reason: ", t);
            }
        }

        public void contextDestroyed(ServletContextEvent servletContextEvent) {
        }
    }

}
