var DEBUG = false;

// User info retrieved from chats is store in this object
var context = {};

// Cache conversation position
var conversationPos;

// Cache conversation status
var conversationStarted = false;

/*
 ---------------
 Utility Functions
 ---------------
 */

function getCurrentTime() {
    var date = new Date();
    var hours = date.getHours();
    var minutes = date.getMinutes();
    var ampm = hours >= 12 ? 'PM' : 'AM';
    hours = hours % 12;
    hours = hours ? hours : 12;
    minutes = minutes < 10 ? '0' + minutes : minutes;
    var strTime = hours + ':' + minutes + ' ' + ampm;
    return strTime;
}

/**
 * Cast string
 * 
 * @param string value
 * @returns string
 */
function _phpCastString(value) {
    var type = typeof value

    switch (type) {
        case 'boolean':
            return value ? '1' : ''
        case 'string':
            return value
        case 'number':
            if (isNaN(value)) {
                return 'NAN'
            }

            if (!isFinite(value)) {
                return (value < 0 ? '-' : '') + 'INF'
            }

            return value + ''
        case 'undefined':
            return ''
        case 'object':
            if (Array.isArray(value)) {
                return 'Array'
            }

            if (value !== null) {
                return 'Object'
            }

            return ''
        case 'function':
        // fall through
        default:
            throw new Error('Unsupported value type')
    }
}

/**
 * Strip tags from a string
 * 
 * big thanks to http://locutus.io/php/strings/strip_tags/
 * 
 * @param string input
 * @param string allowed
 * @returns string
 */
function strip_tags(input, allowed) {
    // making sure the allowed arg is a string containing only tags in lowercase (<a><b><c>)
    allowed = (((allowed || '') + '').toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('')

    var tags = /<\/?([a-z0-9]*)\b[^>]*>?/gi
    var commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi

    var after = _phpCastString(input)
    // removes tha '<' char at the end of the string to replicate PHP's behaviour
    after = (after.substring(after.length - 1) === '<') ? after.substring(0, after.length - 1) : after

    // recursively remove tags to ensure that the returned string doesn't contain forbidden tags after previous passes (e.g. '<<bait/>switch/>')
    while (true) {
        var before = after
        after = before.replace(commentsAndPhpTags, '').replace(tags, function ($0, $1) {
            return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : ''
        })

        // return once no more tags are removed
        if (before === after) {
            return after
        }
    }
}

function renderStatement(statement) {
    // Strip html tags from statement
    statement = strip_tags(statement);

    jQuery('.chat-container').append('<div class="chat-message-wrapper"><div class="absolute-wrapper"><div class="message-details"><div class="bot-avatar"></div><span class="message-date">' + getCurrentTime() + '</span></div></div><p class="chat-message">' + statement + '</p></div>');
}

function showTyping() {
    jQuery('.chat-container').append('<div class="typing-wrapper"><div class="bot-avatar"></div><span class="bot-name">' + botName + '</span><p class="chat-message typing"><span class="dot"></span><span class="dot"></span><span class="dot"></span></p></div>');
}

function hideTyping() {
    jQuery('.typing-wrapper').remove();
}

var chatWrapper = jQuery('.bot-chat-wrapper');

function scrollToBottom() {
    chatWrapper.animate({
        scrollTop: 600
    }, "slow");
}

/**
 * Input checking
 */
function inputError(msg) {
    jQuery('.bot-error p').text(msg);
    jQuery('.bot-error')
            .animate({bottom: 0}, 500)
            .delay(3000)
            .animate({bottom: "-70px"}, 500);
}

function checkInput(option) {
    var input = jQuery('.bot-container input[type=text]').val();

    // Strip html tags from input
    input = strip_tags(input);

    if (input.length > 2) {
        showResponse(option);
    } else {
        inputError(botVars.validationName);
    }

    return false;
}


function checkEmail(option) {
    var input = jQuery('.bot-container input[type=email]').val();
    var regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    var result = regex.test(String(input.toLowerCase()));

    if (input.length > 7 && result === true) {

        // Add new entry to our db
        var bot_subscribe_form = jQuery('.bot_subscribe_form').serialize();
        var subscribe_bot_data = 'action=wpmm_add_subscriber&' + bot_subscribe_form;

        jQuery.post(wpmm_vars.ajax_url, subscribe_bot_data, function (response) {
            if (!response.success) {
                alert(response.data);
                return false;
            }
        }, 'json');

        showResponse(option);

    } else {
        inputError(botVars.validationEmail);
    }
    return false;
}

function clearChat() {
    jQuery('.chat-container').empty();
}

function clearFooter() {
    jQuery('.choices').empty();
    jQuery('.input').empty();
}

/*
 ------------------------
 Setup Conversation Data
 ------------------------
 */

function startConversation(conv, pos) {
    /* We need this to maintain backward compatibility because we moved startConversation() from /views/maintenance.php to bot.js */
    if (conversationStarted) {
        return false;
    }

    conversationStarted = true;

    clearFooter();
    clearChat();

    // Set conversation position
    // 'conversation' is in the global scope
    conversationPos = conv;

    // Load conversation data
    jQuery.getScript(botVars.uploadsBaseUrl + "data.js", function (data) {
        // Show first bot statement
        showStatement(pos);

    });

}

/*
 -------------------
 Show Bot Statement
 -------------------
 */
function showStatement(pos) {

    // Where are we in conversationData?
    var node = conversationData[conversationPos][pos];

    // If there is a side effect execute that within the context
    if ('sideeffect' in node && jQuery.type(node['sideeffect'] === "function")) {
        node['sideeffect'](context);
    }

    // Wrap the statements in an array (if they're not already)
    var statements;
    if (jQuery.type(node['statement']) === "array") {
        statements = node['statement'];
    } else if (jQuery.type(node['statement']) === "string") {
        statements = [node['statement']];
    } else if (jQuery.type(node['statement']) === "function") {
        statements = node['statement'](context);
    }

    /*
     ------------------------
     Render Bot Statement(s)
     ------------------------
     Run this function over each statement
     */
    async.eachSeries(statements, function (item, callback) {
        // Emulate typing then scroll to bottom
        showTyping();
        scrollToBottom();

        // Create random delay
        // If statement is short, delay 1.8 seconds
        // Else, random delay based on statement length
        if (item.length <= 50) {
            var delay = 1800;
        } else {
            var delay = (item.length / 3) * 30 * (Math.floor(Math.random() * 5) + 1.2);
        }

        if (DEBUG) {
            delay = 0;
        }


        setTimeout(function () {
            hideTyping();
            renderStatement(item);
            scrollToBottom();

            callback();
        }, delay);
    },
            /*
             ----------------------
             Render User Option(s)
             ----------------------
             This is the final callback of the series
             */
                    function (err) {

                        /*
                         ----------------------------
                         If User Option is Button(s)
                         ----------------------------
                         */
                        if ('options' in node) {
                            jQuery('.input').hide();
                            jQuery('.choices').show();

                            // Get the options' data
                            var options = node["options"];

                            // If there are options render them
                            // Otherwise this is the end
                            if (options.length > 0) {

                                // Pause 750ms, then render options
                                setTimeout(function () {

                                    for (var i = 0; i < options.length; i++) {
                                        var option = options[i];
                                        var extraClass;
                                        var clickFunction;

                                        // Check option for a consequence
                                        if (option['consequence'] === null) {

                                            // The consequence is null meaning this is a branch we won't be exploring
                                            // The button is given class 'disabled' and does nothing on click
                                            clickFunction = null;
                                            extraClass = "disabled";

                                        } else {

                                            // Else, click function (showResponse) is binded to it
                                            clickFunction = function (option) {
                                                showResponse(option);
                                            }.bind(null, option);

                                            extraClass = "";

                                        }

                                        // Render button
                                        var button = jQuery('<p/>', {
                                            text: option['choice'],
                                            "class": "chat-message user",
                                            click: clickFunction
                                        }).appendTo('.choices');
                                    }

                                }, 750);

                            }

                            /*
                             ------------------------
                             If User Option is Input
                             ------------------------
                             */
                        } else if ('input' in node) {
                            jQuery('.input').show();
                            jQuery('.choices').hide();

                            var option = node['input'];


                            /*
                             Render Input
                             ---------------
                             */

                            // Create a form to hold our input and submit button
                            var form = jQuery('<form/>', {
                                submit: checkInput.bind(null, option)
                            });

                            // Create a user bubble, append to form
                            var inputBubble = jQuery('<p/>', {
                                "class": "chat-message user"
                            }).appendTo(form);

                            // Create an input, append to user bubble
                            var input = jQuery('<input/>', {
                                type: 'text',
                                placeholder: botVars.typeName,
                                name: option['name'],
                                autocomplete: 'off',
                                required: true
                            }).appendTo(inputBubble);

                            // Create an input button, append to user bubble
                            var button = jQuery('<a/>', {
                                text: botVars.send,
                                click: checkInput.bind(null, option)
                            }).appendTo(inputBubble);

                            // Append form to div.input
                            form.appendTo('.input');

                            // Focus on the input we just put into the DOM
                            async.nextTick(function () {
                                input.focus();
                            });

                            /*
                             ------------------------
                             If User Option is Email
                             ------------------------
                             */
                        } else if ('email' in node) {
                            jQuery('.input').show();
                            jQuery('.choices').hide();

                            var option = node['email'];

                            /*
                             Render Input
                             ---------------
                             */

                            // Create a form to hold our input and submit button
                            var form = jQuery('<form/>', {
                                "class": "bot_subscribe_form",
                                submit: checkEmail.bind(null, option)
                            });

                            // Create a user bubble, append to form
                            var inputBubble = jQuery('<p/>', {
                                "class": "chat-message user"
                            }).appendTo(form);

                            // Create hidden input, append to user bubble
                            var input = jQuery('<input/>', {
                                type: 'hidden',
                                name: '_wpnonce',
                                value: botVars.wpnonce
                            }).appendTo(inputBubble);

                            // Create email input, append to user bubble
                            var input = jQuery('<input/>', {
                                type: 'email',
                                placeholder: botVars.typeEmail,
                                name: option['email'],
                                autocomplete: 'off'
                            }).appendTo(inputBubble);

                            // Create an input button, append to user bubble
                            var button = jQuery('<a/>', {
                                text: botVars.send,
                                // "class": "user-email-trigger",
                                click: checkEmail.bind(null, option)
                            }).appendTo(inputBubble);

                            // Append form to div.input
                            form.appendTo('.input');

                            // Focus on the input we just put into the DOM
                            async.nextTick(function () {
                                input.focus();
                            });

                        }

                        scrollToBottom();
                    });
        }

/*
 ---------------------
 Render User Response
 ---------------------
 */
function showResponse(option) {

    // If there was an input element, put that into the global context
    var feedback = "";

    if ('name' in option) {
        context[option['name']] = jQuery('.bot-container input[type=text]').val();
        feedback = context[option['name']];
    } else if ('email' in option) {
        context[option['email']] = jQuery('.bot-container input[type=email]').val();
        feedback = context[option['email']];
    } else {
        feedback = option['choice'];
    }

    clearFooter();

    // Strip html tags from feedback
    feedback = strip_tags(feedback);

    // Show what the user chose
    jQuery(".chat-container").append('<p class="chat-message user">' + feedback + '</p>');

    if ('consequence' in option) {
        showStatement(option['consequence']);
    } else {
        // xxx
    }
}

/*
 -------------------
 Initialize Conversation
 -------------------
 */

startConversation('homepage', 1);