package com.sendsms;

import java.io.*;
import java.net.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;

import org.json.JSONArray;
import org.json.JSONObject;

@SuppressWarnings("unused")
public class Api {

    private final String username;
    private final String password;
    private boolean needsAuth = true;

    private boolean debugState = false;
    private boolean performActionImmediately = true;
    private final JSONArray queuedActions = new JSONArray();

    /**
     * Instantiates a new instance of the API
     *
     * @param username Your sendSMS.ro username
     * @param password Your sendSMS.ro password
     */

    public Api(String username, String password) {
        this.username = username;
        this.password = password;
    }

    /**
     * Set's the debugging state
     * 
     * (if on it will print lines to the console, default: off)
     *
     * @param state Debugging state
     */
    @SuppressWarnings("unused")
    public void setDebugState(boolean state) {
        this.debugState = state;
    }

    @SuppressWarnings("unused")
    public void setPerformActionImmediately(boolean state) {
        this.performActionImmediately = state;
    }

    private static HashMap createParams(String action) {
        HashMap map = new HashMap<>();
        map.put("action", action);
        return map;
    }

    private static void addParameter(Map map, String key, String value) {
        if (value != null) {
            map.put(key, value);
        }
    }

    private static void addParameter(Map map, String key, Integer value) {
        if (value != null) {
            map.put(key, String.valueOf(value));
        }
    }

    private static void addParameter(Map map, String key, Float value) {
        if (value != null) {
            map.put(key, String.valueOf(value));
        }
    }

    private static void addParameter(Map map, String key, Long value) {
        if (value != null) {
            map.put(key, String.valueOf(value));
        }
    }

    private static void addParameter(HashMap map, String key, boolean value) {
        if (value) {
            map.put(key, "true");
        } else {
            map.put(key, "false");
        }
    }

    private static String convertStreamToString(InputStream is) {
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();

        String line;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line).append("\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }

    private JSONObject call(Map map) {
        try {
            if(performActionImmediately) {
                String apiUrl = "https://api.sendsms.ro/json";
                StringBuilder workingUrl = new StringBuilder(apiUrl);

                URL url = createURL(map, workingUrl);
                HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
                String result = convertStreamToString(urlConnection.getInputStream());

                if (result.length() > 0) {
                    /* Something */
                    return new JSONObject(result);
                }
            } else {
                JSONObject action = new JSONObject();
                action.put("command", map.get("action"));
                JSONObject params = new JSONObject();

                String val;
                String key;

                Set keys = map.keySet();

                for (String s : keys) {
                    key = s;
                    val = map.get(key);

                    if(!s.equals("action")) {
                        params.put(s, val);
                    }
                }

                queuedActions.put(action);
                action.put("params", params);
            }
        } catch (ConnectException e) {
            debug("Connection error " + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private URL createURL(Map map, StringBuilder workingUrl) throws UnsupportedEncodingException, MalformedURLException {
        String val;
        String key;
        String encodingScheme = "UTF-8";
        if(needsAuth) {
            if(username != null && password != null) {
                workingUrl.append("?username=").append(URLEncoder.encode(username, encodingScheme));
                workingUrl.append("&password=").append(URLEncoder.encode(password, encodingScheme));
            } else {
                debug("Please specify an username and a password");
            }
        }

        Set keys = map.keySet();


        for (String s : keys) {
            // check this for possible errors
            key = s;
            val = map.get(key);
            if (val != null) {
                if(needsAuth) {
                    workingUrl.append("&").append(URLEncoder.encode(key, encodingScheme)).append("=").append(URLEncoder.encode(val, encodingScheme));
                } else {
                    needsAuth = true;
                    workingUrl.append("?").append(URLEncoder.encode(key, encodingScheme)).append("=").append(URLEncoder.encode(val, encodingScheme));
                }
            }
        }

        debug("URL to be called " + workingUrl);

        return new URL(workingUrl.toString());
    }

    private boolean validateResult(JSONObject json) {
        if (json != null) {
            try {
                int status = json.getInt("status");

                if (status >= 0) {
                    /* OK! */
                    return true;
                }
                debug("result was " + json.toString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    private void debug(String data) {
        if (this.debugState) {
            Calendar cal = Calendar.getInstance();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String str = "[" + sdf.format(cal.getTime()) + "] " + data;
            System.out.println(str);
        }
    }

    @SuppressWarnings("unused")
    public static boolean isNullOrEmpty(String str) {
        return str == null || str.isEmpty();
    }

    /**
     * Get a list of contacts for a particular group
     *
     * @param group_id The id of the group
     * @return A JSONArray containing a list of contacts
     */
    @SuppressWarnings("unused")
    public JSONArray address_book_contacts_get_list(Integer group_id) {
        HashMap map = createParams("address_book_contacts_get_list");
        addParameter(map, "group_id", group_id);

        JSONObject result = call(map);

        if (validateResult(result)) {
            try {
                return result.getJSONArray("details");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Add a contact to a group
     *
     * @param group_id     The group ID to add the record to
     * @param phone_number Phone number of the user
     * @param first_name   The first name of the new contact
     * @param last_name    The last name of the new contacts
     * @return The ID of the contact
     */
    @SuppressWarnings("unused")
    public Integer address_book_contact_add(Integer group_id, String phone_number, String first_name, String last_name) {
        HashMap map = createParams("address_book_contact_add");
        addParameter(map, "group_id", group_id);
        addParameter(map, "phone_number", phone_number);
        addParameter(map, "first_name", first_name);
        addParameter(map, "last_name", last_name);

        JSONObject result = call(map);

        if (validateResult(result)) {
            try {
                return result.getInt("details");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Delete a contact
     *
     * @param contact_id The id of the contact
     * @return true/false
     */
    @SuppressWarnings("unused")
    public boolean address_book_contact_delete(Integer contact_id) {
        HashMap map = createParams("address_book_contact_delete");
        addParameter(map, "contact_id", contact_id);
        JSONObject result = call(map);

        return validateResult(result);
    }

    /**
     * Update a contact
     *
     * @param contact_id   The id of the contact
     * @param phone_number The new phone number of the contact
     * @param first_name The new first name of the contact
     * @param last_name The new last name of the contact
     * @return true/false
     */
    @SuppressWarnings("unused")
    public boolean address_book_contact_update(Integer contact_id, String phone_number, String first_name, String last_name) {
        HashMap map = createParams("address_book_contact_update");
        addParameter(map, "contact_id", contact_id);
        addParameter(map, "phone_number", phone_number);
        addParameter(map, "first_name", first_name);
        addParameter(map, "last_name", last_name);
        JSONObject result = call(map);

        return validateResult(result);
    }

    /**
     * Get a list of all the groups
     *
     * @return A JSONArray containing a list of contacts
     */
    @SuppressWarnings("unused")
    public JSONArray address_book_groups_get_list() {
        HashMap map = createParams("address_book_groups_get_list");

        JSONObject result = call(map);

        if (validateResult(result)) {
            try {
                return result.getJSONArray("details");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Add a new group
     *
     * @param name The name of the new group
     * @return The id of the group
     */
    @SuppressWarnings("unused")
    public Integer address_book_group_add(String name) {
        HashMap map = createParams("address_book_group_add");
        addParameter(map, "name", name);

        JSONObject result = call(map);

        if (validateResult(result)) {
            try {
                return result.getInt("details");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Delete a group
     *
     * @param group_id The ID of the group
     * @return true/false
     */
    @SuppressWarnings("unused")
    public boolean address_book_group_delete(Integer group_id) {
        HashMap map = createParams("address_book_group_delete");
        addParameter(map, "group_id", group_id);
        JSONObject result = call(map);

        return validateResult(result);
    }

    /**
     * Retrieves a list of the user batches.
     *
     * @return A JSONArray containing user batches
     */
    @SuppressWarnings("unused")
    public JSONArray batches_list() {
        HashMap map = createParams("batches_list");

        JSONObject result = call(map);

        if (validateResult(result) && result.has("details")) {
            try {
                return result.getJSONArray("details");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Checks the status of a batch
     *
     * @param batch_id The ID of the batch
     */
    @SuppressWarnings("unused")
    public JSONObject batch_check_status(Integer batch_id) {
        HashMap map = createParams("batch_check_status");
        addParameter(map, "batch_id", batch_id);

        JSONObject result = call(map);

        if (validateResult(result)) {
            try {
                return result.getJSONObject("details");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Creates a new batch
     *
     * @param name       A description / name for this batch
     * @param file       The file
     * @param throughput Throughput to deliver this batch at (per second)
     * @param filter     Filter this batch against the global blacklist
     * @param file_type  File type of the upload (csv, xls or zip accepted)
     * @param start_time If the batch must be auto-started at a given time, it must be specified here
     * @return batch ID
     */
    @SuppressWarnings("unused")
    public String batch_create(String name, InputStream file, Integer throughput, boolean filter, String file_type, String start_time) throws IOException {
        HashMap map = createParams("batch_create");
        addParameter(map, "name", name);
        addParameter(map, "throughput", throughput);
        addParameter(map, "filter", filter);
        addParameter(map, "file_type", file_type);
        addParameter(map, "start_time", start_time);

        StringBuilder textBuilder = new StringBuilder();
        if (!file_type.equals("zip")) {
            try (Reader reader = new BufferedReader(new InputStreamReader
                    (file, Charset.forName(StandardCharsets.UTF_8.name())))) {
                int c;
                while ((c = reader.read()) != -1) {
                    textBuilder.append((char) c);
                }
            }
        } else {
            int bytesRead;
            int chunkSize = 10000000;
            byte[] chunk = new byte[chunkSize];
            while ((bytesRead = file.read(chunk)) > 0) {
                byte[] ba = new byte[bytesRead];
                System.arraycopy(chunk, 0, ba, 0, ba.length);
                byte[] encStr = Base64.getEncoder().encode(ba);

                textBuilder.append(new String(encStr, StandardCharsets.UTF_8));
            }
        }
        file.close();

        String apiUrl = "https://api.sendsms.ro/json";
        StringBuilder workingUrl = new StringBuilder(apiUrl);
        String key;
        String val;
        try {
            URL url = createURL(map, workingUrl);
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();

            StringBuilder data = new StringBuilder();
            data.append(URLEncoder.encode(textBuilder.toString(), StandardCharsets.UTF_8));

            Map params = new LinkedHashMap<>();
            params.put("data", data);

            StringBuilder postData = new StringBuilder();
            for (Map.Entry param : params.entrySet()) {
                if (postData.length() != 0) postData.append('&');
                postData.append(param.getKey());
                postData.append('=');
                postData.append(param.getValue());
            }

            byte[] postDataBytes = postData.toString().getBytes(StandardCharsets.UTF_8);

            urlConnection.setRequestMethod("POST");
            urlConnection.setDoOutput(true);
            urlConnection.getOutputStream().write(postDataBytes);
            String result = convertStreamToString(urlConnection.getInputStream());

            if (result.length() > 0) {
                return new JSONObject(result).getString("details");
            }
        } catch (ConnectException e) {
            debug("Connection error " + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Starts the given batch
     *
     * @param batch_id batch_id as returned from batches_list (or other batch API's
     */
    @SuppressWarnings("unused")
    public boolean batch_start(Integer batch_id) {
        HashMap map = createParams("batch_start");
        addParameter(map, "batch_id", batch_id);

        JSONObject result = call(map);

        return validateResult(result);
    }

    /**
     * Stops the given batch
     *
     * @param batch_id batch_id as returned from batches_list (or other batch API's)
     */
    @SuppressWarnings("unused")
    public boolean batch_stop(Integer batch_id) {
        HashMap map = createParams("batch_stop");
        addParameter(map, "batch_id", batch_id);

        JSONObject result = call(map);

        return validateResult(result);
    }

    /**
     * Add the given number to block list
     *
     * @param phonenumber Phone Number in international format (eg. 40727363767)
     */
    @SuppressWarnings({"unused", "UnusedReturnValue"})
    public boolean blocklist_add(Long phonenumber) {
        HashMap map = createParams("blocklist_add");
        addParameter(map, "phonenumber", phonenumber);

        JSONObject result = call(map);

        return validateResult(result);
    }

    /**
     * Delete the given number from block list
     *
     * @param phonenumber Phone Number in international format (eg. 40727363767)
     */
    @SuppressWarnings({"unused", "UnusedReturnValue"})
    public boolean blocklist_del(Long phonenumber) {
        HashMap map = createParams("blocklist_del");
        addParameter(map, "phonenumber", phonenumber);

        JSONObject result = call(map);

        return validateResult(result);
    }

    /**
     * Check if the given number is in block list
     *
     * @param phonenumber Phone Number in international format (eg. 40727363767)
     */
    @SuppressWarnings({"unused", "UnusedReturnValue"})
    public boolean blocklist_check(Long phonenumber) {
        HashMap map = createParams("blocklist_check");
        addParameter(map, "phonenumber", phonenumber);

        JSONObject result = call(map);

        return validateResult(result);
    }

    /**
     * Execute multiple actions at once
     * See the docs for more details on it
     *
     * @return A JSONObject containing the response of all the executed tasks
     */
    @SuppressWarnings("unused")
    public JSONObject execute_multiple() throws UnsupportedEncodingException, MalformedURLException {
        StringBuilder textBuilder = new StringBuilder();
        textBuilder.append(queuedActions);

        String apiUrl = "https://api.sendsms.ro/json?action=execute_multiple";
        String encodingScheme = "UTF-8";
        String workingUrl = apiUrl + "&username=" + URLEncoder.encode(username, encodingScheme) +
                "&password=" + URLEncoder.encode(password, encodingScheme);
        debug(workingUrl);
        URL url = new URL(workingUrl);
        try {
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            StringBuilder data = new StringBuilder();
            data.append(URLEncoder.encode(textBuilder.toString(), StandardCharsets.UTF_8));
            Map params = new LinkedHashMap<>();
            params.put("data", data);

            StringBuilder postData = new StringBuilder();
            for (Map.Entry param : params.entrySet()) {
                if (postData.length() != 0) postData.append('&');
                postData.append(param.getKey());
                postData.append('=');
                postData.append(param.getValue());
            }
            byte[] postDataBytes = postData.toString().getBytes(StandardCharsets.UTF_8);
            urlConnection.setRequestMethod("POST");
            urlConnection.setDoOutput(true);
            urlConnection.getOutputStream().write(postDataBytes);
            String result = convertStreamToString(urlConnection.getInputStream());

            if (result.length() > 0) {
                return new JSONObject(result);
            }
        } catch (ConnectException e) {
            debug("Connection error " + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * This performs an HLR request and gives you the result via an HTTP callback
     *
     * @param number Phone number to perform the action on
     * @param report_url This is the URL you want to be called with the resulting information.
     * @return A JSONObject containing the response of all the executed tasks
     */
    @SuppressWarnings("unused")
    public JSONObject hlr_perform(String number, String report_url) {
        HashMap map = createParams("hlr_perform");
        addParameter(map, "number", number);
        addParameter(map, "report_url", report_url);

        JSONObject result = call(map);

        if (validateResult(result)) {
            try {
                return result.getJSONObject("details");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * This performs an HLR request and gives you the result immediately.
     *
     * @param number Phone number to perform the action on
     * @return A JSONObject containing the response of all the executed tasks
     */
    @SuppressWarnings("unused")
    public JSONObject hlr_perform_synchronous(String number) {
        HashMap map = createParams("hlr_perform_synchronous");
        addParameter(map, "number", number);

        JSONObject result = call(map);

        if (validateResult(result)) {
            try {
                return result.getJSONObject("details");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * This performs an MNP request and gives you the result immediately.
     *
     * @param number A phone number
     * @return A JSONObject containing MNP response
     */
    @SuppressWarnings("unused")
    public JSONObject mnp_perform(String number) {
        HashMap map = createParams("mnp_perform");
        addParameter(map, "number", number);

        JSONObject result = call(map);

        if (validateResult(result)) {
            try {
                return result.getJSONObject("details");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * This function returns all inbound (MO) messages for the user which have an ID larger than 'last_id'.
     *
     * @param last_id A phone number
     * @return Check the API for more info
     */
    @SuppressWarnings("unused")
    public JSONObject messages_get(Integer last_id) {
        HashMap map = createParams("messages_get");
        addParameter(map, "last_id", last_id);

        JSONObject result = call(map);

        if (validateResult(result)) {
            try {
                return result;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Statistics for the user account.
     *
     * @param user_id The sub user id that these statistics are for
     * @param start_date Start point of the statistics
     * @param end_date End point of the statistics
     * @return A JSONObject containing statistics about the user
     */
    @SuppressWarnings("unused")
    public JSONObject messages_statistics(String user_id, String start_date, String end_date) {
        HashMap map = createParams("messages_statistics");
        addParameter(map, "user_id", user_id);
        addParameter(map, "start_date", start_date);
        addParameter(map, "end_date", end_date);

        JSONObject result = call(map);

        if (validateResult(result)) {
            try {
                return result;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Send a message via sendSMS.ro
     *
     * @param to                   The destination of the message
     * @param text                 The text of the message
     * @param from                 The source of the message (where the user will see it came from)
     * @param report_mask          Delivery report mask (See API documentation)
     * @param report_url           Delivery report URL (See API documentation)
     * @param charset              Character set encoding (default: UTF-8)
     * @param data_coding          Data coding (See API documentation)
     * @param message_class        Message class
     * @param auto_detect_encoding Auto detect the encoding and send appropriately 1 = on, 0 = off
     * @param short_url            Add short URL (See API documentation)
     * @return Details about the message sent
     */
    @SuppressWarnings("unused")
    public String message_send(String to, String text, String from, Integer report_mask, String report_url, String charset, Integer data_coding, Integer message_class, Integer auto_detect_encoding, String short_url) {
        HashMap map = createParams("message_send");
        addParameter(map, "to", to);
        addParameter(map, "text", text);
        addParameter(map, "from", from);
        addParameter(map, "report_mask", report_mask);
        addParameter(map, "report_url", report_url);
        addParameter(map, "charset", charset);
        addParameter(map, "data_coding", data_coding);
        addParameter(map, "message_class", message_class);
        addParameter(map, "auto_detect_encoding", auto_detect_encoding);
        addParameter(map, "short", short_url);

        JSONObject result = call(map);

        if (validateResult(result)) {
            /* Return the message ID */
            try {
                return result.getString("details");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Send a message via with an unsubscribe link sendSMS.ro
     *
     * @param to                   The destination of the message
     * @param text                 The text of the message
     * @param from                 The source of the message (where the user will see it came from)
     * @param report_mask          Delivery report mask (See API documentation)
     * @param report_url           Delivery report URL (See API documentation)
     * @param charset              Character set encoding (default: UTF-8)
     * @param data_coding          Data coding (See API documentation)
     * @param message_class        Message class
     * @param auto_detect_encoding Auto detect the encoding and send appropriately 1 = on, 0 = off
     * @param short_url            Add short URL (See API documentation)
     * @return Details about the message sent
     */
    @SuppressWarnings("unused")
    public String message_send_gdpr(String to, String text, String from, Integer report_mask, String report_url, String charset, Integer data_coding, Integer message_class, Integer auto_detect_encoding, String short_url) {
        HashMap map = createParams("message_send_gdpr");
        addParameter(map, "to", to);
        addParameter(map, "text", text);
        addParameter(map, "from", from);
        addParameter(map, "report_mask", report_mask);
        addParameter(map, "report_url", report_url);
        addParameter(map, "charset", charset);
        addParameter(map, "data_coding", data_coding);
        addParameter(map, "message_class", message_class);
        addParameter(map, "auto_detect_encoding", auto_detect_encoding);
        addParameter(map, "short", short_url);

        JSONObject result = call(map);

        if (validateResult(result)) {
            /* Return the message ID */
            try {
                return result.getString("details");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Check the status of a message
     *
     * @param message_id The message ID you wish to check
     * @return A JSONObject containing keys status, cost and parts see ApiExample.java
     */
    @SuppressWarnings("unused")
    public JSONObject message_status(String message_id) {
        HashMap map = createParams("message_status");
        addParameter(map, "message_id", message_id);
        JSONObject result = call(map);
        if (validateResult(result)) {
            try {
                return result.getJSONObject("details");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Function to ensure communication
     */
    @SuppressWarnings("unused")
    public JSONObject ping() {
        HashMap map = createParams("ping");
        needsAuth = false;
        JSONObject result = call(map);
        if (validateResult(result)) {
            try {
                return result;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * This action allows you to check the price you can expect to pay for a message to the destination in 'to'
     *
     * @param to The number you wish to test
     * @return A JSONObject containing the cost
     */
    @SuppressWarnings("unused")
    public JSONObject route_check_price(String to) {
        HashMap map = createParams("route_check_price");
        addParameter(map, "to", to);
        JSONObject result = call(map);
        if (validateResult(result)) {
            try {
                return result.getJSONObject("details");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * This action allows a third party application to get an authentication key in order to make use of a user's account.
     *
     * @param application_name The name of the application to authorize
     * @param icon_url The URL where an icon can be found for this application
     * @param return_url The URL where the user must be returned to once complete
     * @return Contains 'authorize_url' where the user should be sent to and 'request_key' which is the key to be used for API key creation once complete
     */
    @SuppressWarnings("unused")
    public String user_authorize_application(String application_name, String icon_url, String return_url) {
        needsAuth = false;
        HashMap map = createParams("user_authorize_application");
        addParameter(map, "application_name", application_name);
        addParameter(map, "icon_url", icon_url);
        addParameter(map, "return_url", return_url);
        JSONObject result = call(map);
        if (validateResult(result)) {
            try {
                return result.getJSONObject("details").getString("request_key");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Get the api key after you call user_allow_token_request()
     *
     * @param request_key The original request key returned from user_authorize_application()
     * @return Contains 'username' and 'key' which can be used as username and password combinations in subsequent requests
     */
    @SuppressWarnings("unused")
    public JSONObject user_get_api_key(String request_key) {
        needsAuth = false;
        HashMap map = createParams("user_get_api_key");
        addParameter(map, "request_key", request_key);
        JSONObject result = call(map);
        if (validateResult(result)) {
            try {
                return result.getJSONObject("details");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Check your balance
     *
     * @return The balance
     */
    @SuppressWarnings("unused")
    public Double user_get_balance() {
        HashMap map = createParams("user_get_balance");
        JSONObject result = call(map);
        if (validateResult(result)) {
            try {
                return result.getDouble("details");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Get user info
     *
     * @return A JSONObject containing user info
     */
    @SuppressWarnings("unused")
    public JSONObject user_get_info() {
        HashMap map = createParams("user_get_info");
        JSONObject result = call(map);
        if (validateResult(result)) {
            try {
                return result.getJSONObject("details");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Get user phone number
     *
     * @return The phone number
     */
    @SuppressWarnings("unused")
    public String user_get_phone_number() {
        HashMap map = createParams("user_get_phone_number");
        JSONObject result = call(map);
        if (validateResult(result)) {
            try {
                return result.getString("details");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Transfer credit from one account to another
     *
     * @param target_username The username to move funds to (this must be a sub-user of your account)
     * @param amount The amount to transfer
     * @return Return true is the operation was successfully
     */
    @SuppressWarnings("unused")
    public boolean user_transfer_funds(String target_username, Float amount) {
        HashMap map = createParams("user_transfer_funds");
        addParameter(map, "target_username", target_username);
        addParameter(map, "amount", amount);

        JSONObject result = call(map);

        return validateResult(result);
    }

    /**
     *   Authorization of the key generated by user_authorize_application
     *
     *   @param request_key The key returned by user_authorize_application
     */
    public Boolean user_allow_token_request(String request_key) {
        HashMap map = createParams("user_allow_token_request");
        addParameter(map, "request_key", request_key);

        JSONObject result = call(map);

        return validateResult(result);
    }

    /**
     *  List all whitelisted IPs of the API Key
     */
    public String token_list_ips()
    {
        HashMap map = createParams("token_list_ips");
        JSONObject result = call(map);
        if (validateResult(result)) {
            try {
                return result.getJSONObject("details").getString("locked_on_ip");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     *   Add a new IP to the API Key whitelist
     *
     *   @param ipaddr: The IP to whitelist
     */
    public JSONObject token_add_ip(String ipaddr)
    {
        HashMap map = createParams("token_add_ip");
        addParameter(map, "ipaddr", ipaddr);
        JSONObject result = call(map);
        if (validateResult(result)) {
            try {
                return result;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}