﻿'use strict';

(function (a) {

    // presenter class
    a.AnotherPresenter = function (name, container) {

        // this
        var ts = this;

        //
        this.Name = name;

        // container
        this.Container = container;

        // dom helper
        this.DomHelper = a.GetDependency("DomHelper");

        // check
        if (a.IsUndefinedOrNull(container) || container.length < 1)
            throw new Error("Presenter: container must be at least one element");

        // element
        this.Element = function (selector) {
            var output = ts.Container.find(selector);
            if (output.length < 1) {
                output = ts.DomHelper("<div />");
            }
            return output;
        }

        // helpers
        this.ShouldBeObservableArray = function (obj) {
            return a.IsArray(obj) && obj.toString() !== "Another.ObservableArray";
        }
        this.Aliases = [];
        this.AddAlias = function (alias, realValStr) {

            // add
            alias = "{" + alias + "}";

            // add to collection
            ts.Aliases.push({ alias: alias, str: realValStr });

        }
        this.Eval = function (str, obj, spltStringArr) {

            //if (str.indexOf("{row})") > -1) debugger;

            // set str
            str = ts.GetUnaliasedString(str, obj, spltStringArr);

            // output
            var output = undefined;

            // try evals
            try {
                eval("output = " + str);
            } catch (err) {
                //if (window.location.toString().indexOf("localhost") > -1)
                // console.log("Error evaluating " + str + ": ",err);
            }

            // 
            return output;

        }
        this.EvalSet = function (str, val) {

            str = ts.GetUnaliasedString(str);
            try {
                eval(str + " = val;");
            } catch (err) {
                //if (window.location.toString().indexOf("localhost") > -1)
                //console.log("Error evaluating " + str);// + ": ",err);
            }
        }
        this.GetUnaliasedString = function (str, obj, spltStringArr) {

            // check
            var includeMethods = false;
            if (str.indexOf("(") > -1 && str.indexOf(")") > -1) {
                includeMethods = true;
            }

            // sort params
            if (spltStringArr === undefined && a.IsArray(obj)) {
                spltStringArr = obj;
                obj = undefined;
            }
            if (spltStringArr === undefined) {
                spltStringArr = ts.SplitAliasedString(str, includeMethods);
            }

            // now find and replace chunks with ts
            // ReSharper disable once QualifiedExpressionMaybeNull
            spltStringArr.forEach(function (match) {
                str = a.ReplaceAll(str, match, "ts." + match);
            });


            // now replace aliases
            ts.Aliases.forEach(function (alias) {

                str = a.ReplaceAll(str, alias.alias, alias.str);

            });

            // now loop obj
            if (!a.IsUndefinedOrNull(obj) && typeof obj === "object") {
                ts.DomHelper.each(obj, function (key) {
                    str = a.ReplaceAll(str, key, "obj['" + key + "']");
                });
            }

            return str;

        };
        this.SplitAliasedString = function (str, includeMethods) {

            // get from cache
            var output = _splits[str];

            // check
            if (output === undefined) {

                // example '{Model}.ShowSomething != {Data}.FuckOff || false'
                // should return ["{Model}.ShowSomething","{Data}.FuckOff"]
                //str = '{Model}.ShowSomething.Cock({e},{el}) != {Data}.FuckOff || false';
                //includeMethods = true;
                //if (ts.Aliases.filter(function(x) { return x.alias === "{row}"; }).length > 0) debugger;

                // loop aliases
                var match;
                var matcher;
                var matchStr;
                var matches = [];
                ts.Aliases.forEach(function (aliasObj) {
                    //if (/*str.indexOf("{row}.ReadOnly") > -1 &&*/ aliasObj.alias === "{row}") console.log(str);
                    matchStr = aliasObj.alias + (includeMethods ? "[^\\s\\*\\/\\%\\!\\=\\|\\+\\-]*" : "[^\\s\\*\\/\\%\!\\=\\|\\+\\-\\(]*");
                    //matchStr = "{row}[^ \*\/%!=|+-\(]*";
                    matcher = new RegExp(matchStr);
                    match = matcher.exec(str);
                    if (!a.IsUndefinedOrNull(match)) {
                        matches.push(match);
                    }
                });
                output = matches.map(function (m) {
                    return a.StripWhitespace(m[0]);
                });
                _splits[str] = output;
            }

            return output;
        }
        var _splits = {};

        // observe and object
        this.Observe = function (aliasedName) {

            if (aliasedName.indexOf("{") < 0 || aliasedName.indexOf("}") < 0) {
                aliasedName = a.ReplaceAll(aliasedName, "{", "");
                aliasedName = a.ReplaceAll(aliasedName, "}", "");
                aliasedName = "{" + aliasedName + "}";
            }
            // get object strings
            var objStrings = ts.SplitAliasedString(aliasedName);

            // loop
            objStrings.forEach(function (str) {

                // get obj
                var obj = ts.Eval(str, objStrings);

                // check if exists, then carry on
                if (!(a.IsUndefinedOrNull(obj) || typeof obj !== "object")) {

                    // check
                    var exists = !a.IsUndefinedOrNull(_alreadyObserving[str]);
                    if (!exists) {

                        // observe
                        Object.observe(obj, function (vals) {
                            onObjectChanged(str, vals);
                        });

                        // add to observing list
                        _alreadyObserving[str] = obj;

                        // next, check for arrays and other objects
                        ts.DomHelper.each(obj, function (key, innerObj) {

                            checkForArraysAndOrObjectsToBind(str, key, innerObj);

                        });

                    }

                }

            });



        }
        var _alreadyObserving = {};
        var checkForArraysAndOrObjectsToBind = function (aliasedName, propName, obj) {

            // start
            var childPropName = propName;

            // try parse num
            if (!isNaN(propName)) {
                childPropName = "[" + childPropName + "]";
            } else {
                childPropName = "." + childPropName;
            }

            // GETTING RID OF THIS - LEGACY - [] - go away
            if (childPropName.indexOf("[") < 0) {
                
                // childname
                var childName = aliasedName + childPropName;

                // array?
                if (ts.ShouldBeObservableArray(obj)) {
                    var theArray = ts.Eval(childName);
                    ts.ConvertToObservableArray(childName, theArray);
                }

                // object
                if (typeof obj === "object") {

                    ts.Observe(childName);

                }

            }

        }
        var onObjectChanged = function (aliasedName, changedValues) {
            console.log(aliasedName + " changed. ", changedValues);
            // add
            changedValues.forEach(function (vl) {

                // new val
                var newVal = vl.object[vl.name];

                // check if add
                if (vl.type === "add") {

                    // check
                    checkForArraysAndOrObjectsToBind(aliasedName, vl.name, newVal);
                }

                // get name that would be in ts.ChangeObservers;
                var changeObserversKey = aliasedName + "." + vl.name;

                // find in observers
                var found = ts.ChangeObservers.filter(function (co) {
                    return co.aliasedName == changeObserversKey;
                }).sort(a.OrderSort);
                found.forEach(function (f) {

                    f.changeFunc(newVal);

                });

                //// find by parent
                //var parentObj = ts.Eval(aliasedName);
                //var found2 = ts.ChangeObservers.filter(function (co) {
                //    return co.aliasedName == aliasedName;
                //}).sort(a.OrderSort);
                //found2.forEach(function (f) {

                //    f.changeFunc(parentObj, vl.name, newVal);

                //});



            });

        }
        this.ConvertToObservableArray = function (aliasedName, arr) {

            var newArr = new a.ObservableArray();
            newArr.initialize(aliasedName, arr);
            ts.EvalSet(aliasedName, newArr);
            return newArr;

        }
        this.ObserveChange = function (executionOrder, aliasedName, changeFunc) {

            if (a.StringIsNullOrEmpty(aliasedName) || !a.IsFunc(changeFunc)) return;

            // get chunks
            var chunks = ts.SplitAliasedString(aliasedName, false);

            // check children
            //ts.ChildPresenters.forEach(function (ch) {
            //    var chChunks = ch.SplitAliasedString(aliasedName, false);
            //    if (chChunks.length > 0) {
            //        chunks.concat(chChunks);
            //    }
            //});

            //loop
            chunks.forEach(function (ch) {
                ts.ChangeObservers.push({ aliasedName: ch, changeFunc: changeFunc, order: executionOrder });
                ts.ChildPresenters.forEach(function (ch2) {
                    ch2.ChangeObservers.push({ aliasedName: ch, changeFunc: changeFunc, order: executionOrder });
                });
            });

            //var initVal = ts.Eval(aliasedName);
            //changeFunc(initVal);

        }
        this.ChangeObservers = [];
        this.ChildPresenters = [];
        this.ParentPresenter = undefined;
        this.ForceObserve = function () {

            // find in observers
            var found = ts.ChangeObservers;
            //var foundParents = ts.ParentPresenter === undefined ? []: ts.ParentPresenter.ChangeObservers;
            //var foundChildren = [];
            //ts.ChildPresenters.forEach(function (ch) {
            //    var foundChildrenInner = ch.ChangeObservers;
            //    foundChildrenInner.forEach(function (inn) {
            //        foundChildren.push(inn);
            //    });
            //});

            // final
            var finalArr =
                found;
            //.concat(foundParents)
            //.concat(foundChildren)
            //.sort(a.OrderSort);

            // loop
            finalArr.forEach(function (obsObj) {
                var theVal = ts.Eval(obsObj.aliasedName);
                obsObj.changeFunc(theVal);
            });
        }

        // initialize dom
        this.InitializeDom = function (domContext) {

            inspectElement(domContext[0]);

        }
        var inspectElement = function (el) {

            // jQuery el
            var jEl = ts.DomHelper(el);

            // recurse?
            var shouldRecurse = true;

            // ordered plugins
            var orderedPlugins = a.GetPluginsAsArrayOrdered();

            // look at presenters
            orderedPlugins.forEach(function (plugin) {
                //ts.DomHelper.each(a.Plugins, function (pluginName, plugin) {

                // vars
                var attrName = plugin.attrName;

                // get attr
                var attr = el.attributes[attrName];

                // check
                if (attr !== undefined) {

                    // init
                    initializeElement(jEl, el, plugin.name, attrName);

                    // set recurse
                    if (a.IsExcludedFromDomTraversal(attrName)) {
                        shouldRecurse = false;
                    }
                }
            });

            // check
            if (shouldRecurse === true) {
                jEl.children().each(function (iii, elelel) {
                    inspectElement(elelel);
                });
            }

        }
        var initializeElement = function (jEl, el, pluginName, attrName) {

            // attrs
            var opts = {};

            // set wrapperPropName in opts
            var finalVal = jEl.attr(attrName);
            opts.main = finalVal;

            // remove attr
            jEl.removeAttr(attrName);

            // set other attributes
            ts.DomHelper.each(el.attributes, function (ai, attr) {

                if (a.StartsWith(attr.name, attrName)) {
                    opts[attr.name.replace(attrName + "-", "")] = attr.value;
                }
            });

            // call presenter.plugins[pluginName](el, opts)
            ts.Plugins[pluginName](jEl, opts);
        }


        // get service
        this.GetService = function (svcName) {
            return a.GetService(svcName);
        }

        // raise event
        this.RaiseEvent = function (evName, obj) {
            a.RaiseEvent(evName, obj);
            return ts;
        }

        // subscribe
        this.SubscribeToEvent = function (evName, func) {
            a.SubscribeToEvent(evName, func);
            return ts;
        }

        // plugins
        this.Plugins = new a.PluginWrapper(ts);

    };


})(Another);