<commit_before>﻿//TODO: Test for large trees
//integrate https://square.github.io/crossfilter/ or similar for working with large data? not sure how tree generation/reading would work with it.
//TODO:
//what about empties , e.g. blah//blah
//or keys that end with the delimiter?
//Keep open state on data change/reload?
//TODO: Put wrappers around ajax so we can do things like error handling, busy indicator (tokens?)
/*!
 * Copyright 2014 Tacklr
 */
; (function (base, $, undefined) {
    //#region Initalize

    base.CM = base.CM || {};
    var CM = base.CM;

    var docReady = $.Deferred();
    $(docReady.resolve);

    var checkedState = {};

    $.when($.get('api/v1/combined'), docReady)
    .done(function (combined) {
        //var params = parseQuery(true);//save in local storage instead?
        var data = combined[0];

        //TODO: Escape non-printable character?
        //data.Delimiter = params['delimiter'] || data.Delimiter || delimiter;

        //Better way to do these transforms?
        data.MemoryLimit = data.MemoryLimit === null ? 'Unknown' : data.MemoryLimit === -1 ? 'Unlimited' : data.MemoryLimit;
        data.MemoryLimitPercent = data.MemoryLimitPercent === null ? 'Unknown' : data.MemoryLimitPercent;
        data.ob_Count = ko.observable(data.Count);
        data.ob_Delimiter = ko.observable(data.Delimiter);
        data.ob_DetailView = ko.observable(data.DetailView);
        data.ob_Entries = ko.observableArray(data.Entries);
        data.ob_EntryTree = ko.computed(function () {
            //Can we template directly off the array somehow instead of building our tree here?
            //TODO: need to somehow keep checked state
            var CacheRoot = {
                Children: {},
                Values: [],
                isEmpty: function () {
                    return this.Values.length === 0 && $.isEmptyObject(this.Children);
                }
            };

            var id_i = 0;//need hash function or something
            var delimiter = data.ob_Delimiter();
            //Build our tree, any advantage to doing it server side?
            $.each(data.ob_Entries(), function (i, cache) {
                var key = cache.Key;
                var keyParts = key.split(delimiter || null).reverse();
                var current = CacheRoot;
                var currentKeyParts = [];
                while (keyParts.length > 1) {
                    var keyPart = keyParts.pop();
                    currentKeyParts.push(keyPart);
                    if (current.Children[keyPart] === undefined) {
                        var currentKey = currentKeyParts.join(delimiter) + delimiter;//Tacking the delimiter on the end should also prevent collisions for different delimiters on checked state (I think). (actually this may not work if set to no delimiter in certain cases (key ending with delimiter))
                        //Can this be done more cleanly?
                        if (checkedState[currentKey] === undefined)
                            checkedState[currentKey] = ko.observable(false);

                        current.Children[keyPart] = new CacheNode(currentKey, keyPart, checkedState[currentKey], 'item-' + id_i++);//need to get the subkey up to this point
                    }
                    current = current.Children[keyPart];
                }
                current.Values.push(new CacheValue(cache, keyParts.pop(), 'item-' + id_i++));//TODO: Prevent duplicates
            });

            return CacheRoot;
        });

        var deferred = data.DetailView === 'Defer';//data.Deferred or view?
        if (deferred) {
            $('#collapse-tree').one('show.bs.collapse', function () {//Can we do this with knockout?
                //just trigger refresh button?
                $('.refresh-loading').removeClass('hidden');

                Ajax.Get('api/v1/cache')//'refresh' url? include freemem/other stats?
                .done(function (response) {
                    data.ob_Entries(response.Entries);
                });
            });
        }

        ko.applyBindings(data);//Tree parts lose open state on delete, need to save the state somehow.
        cacheData = data;
        //window.DERP = data;

        //Clear loading indiciator
        $('.content-loading').fadeOut(function () {
            $(this).remove();
        });
    });

    toastr.options = {
        closeButton: true,
        closeHtml: '<button type="button"><span class="fa fa-times fa-fw"></span></button>',
        //debug: false,//default
        //positionClass: 'toast-top-right',//default
        //onclick: null,//default
        //showDuration: 300,//default
        //hideDuration: 1000,//default
        //timeOut: 5000,//default
        //extendedTimeOut: 1000,//default
        //showEasing: 'swing',//default
        hideEasing: 'linear',
        //showMethod: 'fadeIn',//default
        //hideMethod: 'fadeOut'//default
    };

    //#endregion Initalize

    //#region Properties

    var cacheData = {};
    var delimiter = '/';//because we are using it later, we must always have one. Apparenly we can even have null keys, so use \x00 if we don't want a delimiter? I don't know if a c# key can contain a null.

    //From System.Web.Caching.CacheItemPriority (unlikely to change, probably doesn't need to be dynamic.)
    var cachePriority = {
        '1': 'Low',
        '2': 'Below Normal',
        '3': 'Normal',//Also 'Default'
        '4': 'Above Normal',
        '5': 'High',
        '6': 'Not Removable'
    };

    ko.bindingHandlers.stopBinding = {
        init: function () {
            return { controlsDescendantBindings: true };
        }
    };

    //#endregion Properties

    //#region Templates

    //We would want the details info for these to start with.
    //ko.templates['CacheListTemplate'] =
    //    '<tr>' +
    //        '<td>' +
    //            '<span class="btn-group">' +
    //                '<button type="button" title="Delete Entry" class="btn btn-xs btn-danger" data-bind="click: CM.DeleteNode(Key)"><span class="fa fa-lg fa-trash-o"></span></button>' +
    //                '<button type="button" title="View Entry Details" class="btn btn-xs btn-info" data-bind="click: CM.EntryDetails(Key)"><span class="fa fa-lg fa-info-circle"></span></button>' +
    //            '</span>' +
    //        '</td>' +
    //        '<td data-bind="text: Key"></td>' +
    //        '<td data-bind="text: Type"></td>' +
    //        //other info
    //    '</tr>';

    ko.templates['CacheTreeTemplate'] =
        '<!-- ko if: !$data.isEmpty() -->' +//better check? make it a method of the node/root?
        '<ul>' +
            '<!-- ko foreach: CM.Sort(CM.ObjectAsArray($data.Children), CM.SortCacheKey) -->' +//eww
            '<li>' +
                '<button type="button" title="Delete Prefix" class="btn btn-xs btn-link" data-bind="click: CM.DeleteNode(Key, true)"><span class="fa fa-lg fa-trash-o"></span></button>' +
                //'<button type="button" title="Serialize Prefix" class="btn btn-xs btn-link" data-bind="click: CM.SerializeNode(Key, true)"><span class="fa fa-lg fa-code"></span></button>' +
                '<input type="checkbox" class="expand" data-bind="checked: ob_Checked, attr: { id: Id }, click: CM.BranchExpand" />' +
                '<label data-bind="attr: { for: Id }"><span data-bind="text: Text"></span> <span class="delimiter" data-bind="text: $root.Delimiter"></span></label>' +
                '<!-- ko template: { name: "CacheTreeTemplate", data: $data } --><!-- /ko -->' +
            '</li>' +
            '<!-- /ko -->' +
            '<!-- ko foreach: CM.Sort($data.Values, CM.SortCacheKey) -->' +//eww
            '<li>' +//Make delete button last tab index?
                '<button type="button" title="Delete Entry" class="btn btn-xs btn-link" data-bind="click: CM.DeleteNode(Key)"><span class="fa fa-lg fa-trash-o"></span></button>' +
                '<button type="button" title="View Entry Details" class="btn btn-xs btn-link" data-bind="click: CM.EntryDetails(Key)"><span class="fa fa-lg fa-info-circle"></span></button>' +
                '<span data-bind="text: Text, attr: { title: Key }"></span>' +// <span class="text-muted">(<span data-bind="text: Type"></span>)</span>
            '</li>' +
            '<!-- /ko -->' +
        '</ul>' +
        '<!-- /ko -->';

    ko.templates['EntryDetailsTemplate'] =
        '<div class="modal-header" tabindex="-1">' +
            '<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="fa fa-times fa-fw"></span></button>' +//styled ×?
            '<h4 class="modal-title" id="modal-title">Entry Details</h4>' +
        '</div>' +
        '<div class="modal-body">' +
            '<div class="row">' +
                '<div class="col-xs-12">' +
                    '<div class="table-responsive">' +
                        '<table class="table table-bordered table-condensed">' +//Better way to display this? independent columns? collapsable?
                            '<tbody>' +
                                '<tr>' +
                                    '<th class="col-fit text-right">Key</th>' +
                                    '<td class="break-all" data-bind="text: Key"></td>' +
                                '</tr>' +
                                '<tr>' +
                                    '<th class="col-fit text-right">Type</th>' +
                                    '<td data-bind="text: Type"></td>' +
                                '</tr>' +
                                '<tr>' +
                                    '<th class="col-fit text-right">Priority</th>' +
                                    '<td data-bind="text: Priority"></td>' +
                                '</tr>' +
                                '<tr>' +
                                    '<th class="col-fit text-right">Created</th>' +
                                    '<td data-bind="text: Created"></td>' +
                                '</tr>' +
                                '<tr>' +
                                    '<th class="col-fit text-right">Absolute Expiration</th>' +
                                    '<td data-bind="text: AbsoluteExpiration"></td>' +
                                '</tr>' +
                                '<tr>' +
                                    '<th class="col-fit text-right">Sliding Expiration</th>' +
                                    '<td data-bind="text: SlidingExpiration"></td>' +
                                '</tr>' +
                            '</tbody>' +<commit_msg>Break long types on info modal. Part of #2.<commit_after>﻿//TODO: Test for large trees
//integrate https://square.github.io/crossfilter/ or similar for working with large data? not sure how tree generation/reading would work with it.
//TODO:
//what about empties , e.g. blah//blah
//or keys that end with the delimiter?
//Keep open state on data change/reload?
//TODO: Put wrappers around ajax so we can do things like error handling, busy indicator (tokens?)
/*!
 * Copyright 2014 Tacklr
 */
; (function (base, $, undefined) {
    //#region Initalize

    base.CM = base.CM || {};
    var CM = base.CM;

    var docReady = $.Deferred();
    $(docReady.resolve);

    var checkedState = {};

    $.when($.get('api/v1/combined'), docReady)
    .done(function (combined) {
        //var params = parseQuery(true);//save in local storage instead?
        var data = combined[0];

        //TODO: Escape non-printable character?
        //data.Delimiter = params['delimiter'] || data.Delimiter || delimiter;

        //Better way to do these transforms?
        data.MemoryLimit = data.MemoryLimit === null ? 'Unknown' : data.MemoryLimit === -1 ? 'Unlimited' : data.MemoryLimit;
        data.MemoryLimitPercent = data.MemoryLimitPercent === null ? 'Unknown' : data.MemoryLimitPercent;
        data.ob_Count = ko.observable(data.Count);
        data.ob_Delimiter = ko.observable(data.Delimiter);
        data.ob_DetailView = ko.observable(data.DetailView);
        data.ob_Entries = ko.observableArray(data.Entries);
        data.ob_EntryTree = ko.computed(function () {
            //Can we template directly off the array somehow instead of building our tree here?
            //TODO: need to somehow keep checked state
            var CacheRoot = {
                Children: {},
                Values: [],
                isEmpty: function () {
                    return this.Values.length === 0 && $.isEmptyObject(this.Children);
                }
            };

            var id_i = 0;//need hash function or something
            var delimiter = data.ob_Delimiter();
            //Build our tree, any advantage to doing it server side?
            $.each(data.ob_Entries(), function (i, cache) {
                var key = cache.Key;
                var keyParts = key.split(delimiter || null).reverse();
                var current = CacheRoot;
                var currentKeyParts = [];
                while (keyParts.length > 1) {
                    var keyPart = keyParts.pop();
                    currentKeyParts.push(keyPart);
                    if (current.Children[keyPart] === undefined) {
                        var currentKey = currentKeyParts.join(delimiter) + delimiter;//Tacking the delimiter on the end should also prevent collisions for different delimiters on checked state (I think). (actually this may not work if set to no delimiter in certain cases (key ending with delimiter))
                        //Can this be done more cleanly?
                        if (checkedState[currentKey] === undefined)
                            checkedState[currentKey] = ko.observable(false);

                        current.Children[keyPart] = new CacheNode(currentKey, keyPart, checkedState[currentKey], 'item-' + id_i++);//need to get the subkey up to this point
                    }
                    current = current.Children[keyPart];
                }
                current.Values.push(new CacheValue(cache, keyParts.pop(), 'item-' + id_i++));//TODO: Prevent duplicates
            });

            return CacheRoot;
        });

        var deferred = data.DetailView === 'Defer';//data.Deferred or view?
        if (deferred) {
            $('#collapse-tree').one('show.bs.collapse', function () {//Can we do this with knockout?
                //just trigger refresh button?
                $('.refresh-loading').removeClass('hidden');

                Ajax.Get('api/v1/cache')//'refresh' url? include freemem/other stats?
                .done(function (response) {
                    data.ob_Entries(response.Entries);
                });
            });
        }

        ko.applyBindings(data);//Tree parts lose open state on delete, need to save the state somehow.
        cacheData = data;
        //window.DERP = data;

        //Clear loading indiciator
        $('.content-loading').fadeOut(function () {
            $(this).remove();
        });
    });

    toastr.options = {
        closeButton: true,
        closeHtml: '<button type="button"><span class="fa fa-times fa-fw"></span></button>',
        //debug: false,//default
        //positionClass: 'toast-top-right',//default
        //onclick: null,//default
        //showDuration: 300,//default
        //hideDuration: 1000,//default
        //timeOut: 5000,//default
        //extendedTimeOut: 1000,//default
        //showEasing: 'swing',//default
        hideEasing: 'linear',
        //showMethod: 'fadeIn',//default
        //hideMethod: 'fadeOut'//default
    };

    //#endregion Initalize

    //#region Properties

    var cacheData = {};
    var delimiter = '/';//because we are using it later, we must always have one. Apparenly we can even have null keys, so use \x00 if we don't want a delimiter? I don't know if a c# key can contain a null.

    //From System.Web.Caching.CacheItemPriority (unlikely to change, probably doesn't need to be dynamic.)
    var cachePriority = {
        '1': 'Low',
        '2': 'Below Normal',
        '3': 'Normal',//Also 'Default'
        '4': 'Above Normal',
        '5': 'High',
        '6': 'Not Removable'
    };

    ko.bindingHandlers.stopBinding = {
        init: function () {
            return { controlsDescendantBindings: true };
        }
    };

    //#endregion Properties

    //#region Templates

    //We would want the details info for these to start with.
    //ko.templates['CacheListTemplate'] =
    //    '<tr>' +
    //        '<td>' +
    //            '<span class="btn-group">' +
    //                '<button type="button" title="Delete Entry" class="btn btn-xs btn-danger" data-bind="click: CM.DeleteNode(Key)"><span class="fa fa-lg fa-trash-o"></span></button>' +
    //                '<button type="button" title="View Entry Details" class="btn btn-xs btn-info" data-bind="click: CM.EntryDetails(Key)"><span class="fa fa-lg fa-info-circle"></span></button>' +
    //            '</span>' +
    //        '</td>' +
    //        '<td data-bind="text: Key"></td>' +
    //        '<td data-bind="text: Type"></td>' +
    //        //other info
    //    '</tr>';

    ko.templates['CacheTreeTemplate'] =
        '<!-- ko if: !$data.isEmpty() -->' +//better check? make it a method of the node/root?
        '<ul>' +
            '<!-- ko foreach: CM.Sort(CM.ObjectAsArray($data.Children), CM.SortCacheKey) -->' +//eww
            '<li>' +
                '<button type="button" title="Delete Prefix" class="btn btn-xs btn-link" data-bind="click: CM.DeleteNode(Key, true)"><span class="fa fa-lg fa-trash-o"></span></button>' +
                //'<button type="button" title="Serialize Prefix" class="btn btn-xs btn-link" data-bind="click: CM.SerializeNode(Key, true)"><span class="fa fa-lg fa-code"></span></button>' +
                '<input type="checkbox" class="expand" data-bind="checked: ob_Checked, attr: { id: Id }, click: CM.BranchExpand" />' +
                '<label data-bind="attr: { for: Id }"><span data-bind="text: Text"></span> <span class="delimiter" data-bind="text: $root.Delimiter"></span></label>' +
                '<!-- ko template: { name: "CacheTreeTemplate", data: $data } --><!-- /ko -->' +
            '</li>' +
            '<!-- /ko -->' +
            '<!-- ko foreach: CM.Sort($data.Values, CM.SortCacheKey) -->' +//eww
            '<li>' +//Make delete button last tab index?
                '<button type="button" title="Delete Entry" class="btn btn-xs btn-link" data-bind="click: CM.DeleteNode(Key)"><span class="fa fa-lg fa-trash-o"></span></button>' +
                '<button type="button" title="View Entry Details" class="btn btn-xs btn-link" data-bind="click: CM.EntryDetails(Key)"><span class="fa fa-lg fa-info-circle"></span></button>' +
                '<span data-bind="text: Text, attr: { title: Key }"></span>' +// <span class="text-muted">(<span data-bind="text: Type"></span>)</span>
            '</li>' +
            '<!-- /ko -->' +
        '</ul>' +
        '<!-- /ko -->';

    ko.templates['EntryDetailsTemplate'] =
        '<div class="modal-header" tabindex="-1">' +
            '<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="fa fa-times fa-fw"></span></button>' +//styled ×?
            '<h4 class="modal-title" id="modal-title">Entry Details</h4>' +
        '</div>' +
        '<div class="modal-body">' +
            '<div class="row">' +
                '<div class="col-xs-12">' +
                    '<div class="table-responsive">' +
                        '<table class="table table-bordered table-condensed">' +//Better way to display this? independent columns? collapsable?
                            '<tbody>' +
                                '<tr>' +
                                    '<th class="col-fit text-right">Key</th>' +
                                    '<td class="break-all" data-bind="text: Key"></td>' +
                                '</tr>' +
                                '<tr>' +
                                    '<th class="col-fit text-right">Type</th>' +
                                    '<td class="break-all" data-bind="text: Type"></td>' +
                                '</tr>' +
                                '<tr>' +
                                    '<th class="col-fit text-right">Priority</th>' +
                                    '<td data-bind="text: Priority"></td>' +
                                '</tr>' +
                                '<tr>' +
                                    '<th class="col-fit text-right">Created</th>' +
                                    '<td data-bind="text: Created"></td>' +
                                '</tr>' +
                                '<tr>' +
                                    '<th class="col-fit text-right">Absolute Expiration</th>' +
                                    '<td data-bind="text: AbsoluteExpiration"></td>' +
                                '</tr>' +
                                '<tr>' +
                                    '<th class="col-fit text-right">Sliding Expiration</th>' +
                                    '<td data-bind="text: SlidingExpiration"></td>' +
                                '</tr>' +
                            '</tbody>' +
{'fraction_non_alphanumeric': 0.15896496665727436, 'fraction_numerical': 0.003709965248426787, 'mean_word_length': 1.7144678138942002, 'pattern_counts': {'":': 0, '<': 235, '<?xml version=': 0, '>': 237, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 48, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2365542', 'n_tokens_mistral': 5929, 'n_tokens_neox': 5778, 'n_words': 1447}