/**
 * loot.game JS SDK
 * ©  2017-2019
 *
 * lootgame.js - Handle all the public side of the SDK
 * 
 * Authors : - ANSELMO Vincent
 *					 - JARDE Pascal
 */

LootSDK = (function (doc) {

	// -- Private Variables ---------------------------------------------------
	
	// -- helpers --
	var _toString = Object.prototype.toString;

	// -- SDK --
	var _debug = true;
	var _version="1.0";
	var _config = { originUrl :  window.location.href, originHost :  window.location.host };

	// -- system --
	var _privateEventCallbacks = ['core:ready', 'signin', 'signout']; //declare all existing private events here
	var _publicEventCallbacks = ['init','connect','disconnect', 'modal:open','modal:close','lottery:result','auth:request']; //declare all existing public events here
	_publicEventCallbacks.forEach(function(event) { _publicEventCallbacks[event] = []; }); 
	_privateEventCallbacks.forEach(function(event) { _privateEventCallbacks[event] = []; });
	
	var _coreFrame = null; //coreFrame is the loaded iframe with the loot sdk core js
	var _coreIsReady = false;
	
	// var _currentAccount = null; //DEPRECATED

	// -- Helpers ---------------------------------------------------

	(function(funcName, baseObj) {
		"use strict";
		// The public function name defaults to window.docReady
		// but you can modify the last line of this function to pass in a different object or method name
		// if you want to put them in a different namespace and those will be used instead of 
		// window.docReady(...)
		funcName = funcName || "docReady";
		baseObj = baseObj || window;
		var readyList = [];
		var readyFired = false;
		var readyEventHandlersInstalled = false;
		
		// call this when the document is ready
		// this function protects itself against being called more than once
		function ready() {
			if (!readyFired) {
				// this must be set to true before we start calling callbacks
				readyFired = true;
				for (var i = 0; i < readyList.length; i++) {
					// if a callback here happens to add new ready handlers,
					// the docReady() function will see that it already fired
					// and will schedule the callback to run right after
					// this event loop finishes so all handlers will still execute
					// in order and no new ones will be added to the readyList
					// while we are processing the list
					readyList[i].fn.call(window, readyList[i].ctx);
				}
				// allow any closures held by these functions to free
				readyList = [];
			}
		}
		
		function readyStateChange() {
			if ( document.readyState === "complete" ) {
				ready();
			}
		}

		// This is the one public interface
		// docReady(fn, context);
		// the context argument is optional - if present, it will be passed
		// as an argument to the callback
		baseObj[funcName] = function(callback, context) {
			if (typeof callback !== "function") {
				throw new TypeError("callback for docReady(fn) must be a function");
			}
			// if ready has already fired, then just schedule the callback
			// to fire asynchronously, but right away
			if (readyFired) {
				setTimeout(function() {callback(context);}, 1);
				return;
			} else {
				// add the function and context to the list
				readyList.push({fn: callback, ctx: context});
			}
			// if document already ready to go, schedule the ready function to run
			// IE only safe when readyState is "complete", others safe when readyState is "interactive"
			if (document.readyState === "complete" || (!document.attachEvent && document.readyState === "interactive")) {
				setTimeout(ready, 1);
			} else if (!readyEventHandlersInstalled) {
				// otherwise if we don't have event handlers installed, install them
				if (document.addEventListener) {
					// first choice is DOMContentLoaded event
					document.addEventListener("DOMContentLoaded", ready, false);
					// backup is window load event
					window.addEventListener("load", ready, false);
				} else {
					// must be IE
					document.attachEvent("onreadystatechange", readyStateChange);
					window.attachEvent("onload", ready);
				}
				readyEventHandlersInstalled = true;
			}
		}
	})("docReady", window);	

	function _setCookie(cname, cvalue, exsec) {
		var d = new Date();
		d.setTime(d.getTime() + (exsec*1000));
		var expires = "expires="+ d.toUTCString();
		document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
	}

	function _getCookie(cname) {
		var name = cname + "=";
		var decodedCookie = decodeURIComponent(document.cookie);
		var ca = decodedCookie.split(';');
		for(var i = 0; i <ca.length; i++) {
			var c = ca[i];
			while (c.charAt(0) == ' ') {
			c = c.substring(1);
			}
			if (c.indexOf(name) == 0) {
			return c.substring(name.length, c.length);
			}
		}
		return "";
	}

	// -- Types Manipulation ---------------------------------------------------
	
	function _isArray(v){return _toString.apply(v) === '[object Array]';};
    function _isEmpty(v, allowBlank){return v === null || v === undefined || ((Array.isArray(v) && !v.length)) || (!allowBlank ? v === '' : false);};
    function _isDate(v){return _toString.apply(v) === '[object Date]';};
    function _isObject(v){return !!v && Object.prototype.toString.call(v) === '[object Object]';};
    function _isPrimitive(v){return this.isString(v) || this.isNumber(v) || this.isBoolean(v);};  
    function _isFunction(v){return _toString.apply(v) === '[object Function]';}; 
    function _isNumber(v){return typeof v === 'number' && isFinite(v);};
    function _isString(v){return typeof v === 'string';};    
    function _isBoolean(v){return typeof v === 'boolean';};    
    function _isElement(v){return typeof v === 'boolean';};   
    function _isDefined(v){return typeof v !== 'undefined';};     
    function _isHexColor(v){return /^#[0-9A-F]{6}$/i.test(v);} //only #FFAACC format (#FFF won't work)
    function _isEmptyObj(obj) {
		for(var prop in obj) {
			if(obj.hasOwnProperty(prop))
				return false;
		}
	
		return JSON.stringify(obj) === JSON.stringify({});
	}
	function _inArray(needle, haystack) {
		var length = haystack.length;
		for(var i = 0; i < length; i++) {
			if(haystack[i] == needle) return true;
		}
		return false;
	}

	// -- Messages between frames --
	
	function _receiveFromCoreFrame(msg) { //Receive a message from the parent
		if(msg.origin!=="https://pkg.loot.game" || msg.data.source != "core")  return;
			
		if (!_isString(msg.data.method)) return false;
		
		if(_debug) console.log("Receive from frame : " + msg.data.method);

		switch(msg.data.method){
			case "core:ready": //Init done, core ready
				if(msg.data.value == 1) _trig('core:ready'), _coreIsReady = 1;
				break;
			case "dsp:connected": //Init done, core ready
				if(msg.data.value == 1) _trig('connect'), _isConnected = true;
				break;
			case "dsp:close": //Init done, core ready
				if(msg.data.value == 1) _trig('disconnect'), _isConnected = false;
				break
			case "coreframe:show":
				if(msg.data.value == 1) _coreFrame.style.display = "block"; //display the modal window
				_trig("modal:open");
				break;
			case "coreframe:hide":
				if(msg.data.value == 1) _coreFrame.style.display = "none"; //display the modal window
				_trig("modal:close");
				break;
				case "account:signin":
					_currentAccount = msg.data.value.account;
					_trig("signin",{id: msg.data.value.account.id, name: msg.data.value.account.name, nickName: msg.data.value.account.name, affiliateID : msg.data.value.account.affiliateID});
					break;
				case "account:signout":
					_currentAccount = null;
					_trig("signout");
					break;
			case "auth:request":
				if(msg.data.value.success){
					_setCookie("loot_userServiceToken",msg.data.value.userServiceToken,30*24*60*60); //30 days
					_trig("auth:request",{success: true, userServiceToken : msg.data.value.userServiceToken});
				} else {
					_trig("auth:request",{success: false,  userServiceToken : null});
				}
				break;
			case "lottery:result":
				_trig("lottery:result",msg.data.value);
				break;
		}
	}
	
	function _sendToCoreFrame(method,value){ //Send a message to the frame
		if(_coreIsReady){
			var receiver = _coreFrame.contentWindow;
			var msg = {};
			msg.method = method;
			msg.value = value;

			if(_debug) console.log("Send to frame : " + method);

			receiver.postMessage(msg, '*');
		} else {
			setTimeout(function(){_sendToCoreFrame(method,value)},250); //coreFrame is not ready, retry in 250ms
		}
	}

	// -- Event callbacks system ---------------------------------------------------
	
	function _on(event,func,public){ //callback declaration

		var callbacks = public ?  _publicEventCallbacks[event] : _privateEventCallbacks[event];

		if(!_isArray(callbacks)) return 0; //This event doesn't exists

		//Prevent multiple addition of the same callback
		for(i=0;i<callbacks.length;i++){
			if(callbacks[i].toString() == func.toString()) return 0;
		}

		//Add the new callback for this event
		if(_isFunction(func)) {
			callbacks.push(func);
			return 1;
		}
		
		return 0;
	}

	function _off(event,func,public){ //callback declaration

		var callbacks = public ?  _publicEventCallbacks[event] : _privateEventCallbacks[event];
		
		if(!_isArray(callbacks)) return 0; //This event doesn't exists

		//Look for the right callback in array, and remove it
		callbacks.forEach( function(callback, index, array) {
			if (callback.toString( ) == func.toString( )) {
				delete array[index]; //delete in the pointing array
				return 1;
			}
		});
		return 0;
	}

	function _trig(event,object){ //trig callbacks of a target event

		if(!_isObject(object)) object = undefined;

		if(_isArray(_privateEventCallbacks[event])){
			_privateEventCallbacks[event].forEach(function(callback) {
				callback(object);
			});
		}
		if(_isArray(_publicEventCallbacks[event])){
			_publicEventCallbacks[event].forEach(function(callback) {
				callback(object);
			});
		}
	}

	// -- SDK initialization ------------------------------------------------------
	docReady(_init); //init after document ready
    function _init(){

		//declare all callbacks :
		_on('core:ready',function(){
			//alert("core frame is ready");
			//_sendToCoreFrame("connect");
		})

		_initIframe();
		
		/* test purpose
		setTimeout(function(){
			_trig('test',{value : 'test'});
		},2000);  */

	}
	
	function _initIframe(){
		//create iframe
		var el = document.createElement("iframe");
		el.id = "LootCoreFrame";

		el.src = "https://pkg.loot.game/"+_version+"/coreFrame/";

		el.allowtransparency = "true";
		el.style.display = "none";
		el.style.zIndex = "2147483647";
		el.style.background = "rgba(0, 0, 0, 0.004) none repeat scroll 0% 0%";
		el.style.border = "0px none transparent";
		el.style.overflowX = "hidden";
		el.style.overflowY = "auto";
		el.style.visibility = "visible";
		el.style.margin = "0px";
		el.style.padding = "0px";
		el.style.position = "fixed";
		el.style.left = "0px";
		el.style.top = "0px";
		el.style.width = "100%";
		el.style.height = "100%";
		el.frameborder = "0";

		_coreFrame = document.body.appendChild(el);
		//_coreFrame.onload = _trig('core:ready');
		
		window.addEventListener('message', _receiveFromCoreFrame);
	}

	// -- Public Methods -------------------------------------------------------
	return {
		init : _init,
		on : function(event, func){ //declare a new callback for a targeted event
			return _on(event, func, true);
		},
		off : function(event,func){ //remove an existing callback for a targeted event
			return _off(event, func, true);
		},
		set : function(param1,param2){ //accept one parameter as object, or 2 parameters (property, value)

			if(!_isDefined(param2)) {
				config = param1;
			} else {
				var config = {};
				config[param1] = param2; 
			}

			if(!_isObject(config)) throw "This function expect an object as first parameter.";

			if(_isDefined(config.serviceID)) {
				if(!_isString(config.serviceID)) throw "The property 'serviceID' expect a string as value.";
				if(!_isDefined(_config.serviceID) && !_isDefined(_config.tokenID)){
					_config.serviceID = config.serviceID; 
				} else throw "The serviceID / tokenID can be defined only once.";
			}

			if(_isDefined(config.tokenID)) {
				if(!_isString(config.tokenID)) throw "The property 'tokenID' expect a string as value.";
				if(!_isDefined(_config.tokenID) && !_isDefined(_config.serviceID)){
					_config.tokenID = config.tokenID; 
				} else throw "The serviceID / tokenID can be defined only once.";
			}
			
			if(_isDefined(config.affiliateID)) if(_isString(config.affiliateID)) _config.affiliateID = config.affiliateID; else throw "The property 'affiliateID' expect a string as value.";

			if(_isDefined(config.promocode)) if(_isString(config.promocode)) _config.promocode = config.promocode; else throw "The property 'promocode' expect a string as value.";

			if(_isDefined(config.hideCloseModalButton)) if(_isBoolean(config.hideCloseModalButton)) _config.hideCloseModalButton = config.hideCloseModalButton; else throw "The property 'hideCloseModalButton' expect a boolean value.";

			//if no exception, send to core :
			_sendToCoreFrame("config:update",_config);
			return 1;
		},
		get : function(param){
			if(!_isDefined(param)) throw "This function expect one parameter.";
			if(!_isString(param))  throw "This function expect a string as first parameter.";
			return _config[param];
		},
		authRequest : function(){
			if (!_isDefined(_config.serviceID) && !_isDefined(_config.tokenID)) throw "You must set your service ID or a token ID first.";

			//check if a token exists :
			var userServiceToken = _getCookie("loot_userServiceToken");

			if(userServiceToken == ""){
				_sendToCoreFrame("auth:request",{});
			} else {
				_trig("auth:request",{success: true, userServiceToken : userServiceToken});
			}
		},
		isAuthenticated : function(){
			var userServiceToken = _getCookie("loot_userServiceToken");

			if( userServiceToken == "" ) {
				return false;
			} else {
				return {success: true, userServiceToken : userServiceToken};
			}
		},
		openUI : function(){
			_sendToCoreFrame("ui:open",{"page":"account"});
			return 1;
		},
		command : function(command){
			if (!_inArray(window.location.host, ['loot.game','my.loot.game'])) throw "Not authorized"; 
			if (!_isDefined(_config.serviceID) && !_isDefined(_config.tokenID)) throw "You must set your service ID or a token ID first.";
			if(command=="signOut") _sendToCoreFrame("account:signout");
			if(command=="isSignedIn") {
				if( typeof _currentAccount !== 'undefined' && _isObject(_currentAccount) ) {
					return {id: _currentAccount.id, name: _currentAccount.name};
				} else {
					return false;
				}
			}
		},
		lottery : function(rule){
			if (!_isDefined(_config.serviceID) && !_isDefined(_config.tokenID)) throw "You must set your service ID or a token ID first.";
			if (!_isDefined(rule)) throw "You must specify a rule in parameter.";

			//check if event's callback is defined =
			if (_publicEventCallbacks['lottery:result'].length == 0) throw "You must set a callback 'lottery:result' before starting a lottery.";
			
			//if(!LootSDK.isSignedIn()) throw "You must signIn first !";

			_sendToCoreFrame("lottery",{"rule":rule});
		}
	};
})(this.document);
;