(function() {

    // global uind object
    window.ctp = {};

    // utils object
    window.ctp.utils = {};

    // date format
    window.ctp.utils.dateFormat = 'm/d/yy';

    // month names
    const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

    // datatable default config
    window.ctp.dataTableDefaultConfig = {
        pageLength: 100,
        searching: true,
        lengthChange: false,
        processing: true,
        fixedHeader: true,
        dom: `
            <'row'<'col-sm-5'B><'col-sm-7'f>>
            <'row'<'col-sm-12't>>
            <'row'<'col-sm-5'i><'col-sm-7'p>>`,
        buttons: [{
            extend: 'excel',
            text: '',
            className: 'bi bi-file-earmark-spreadsheet btn-sm'
        }]
    };

    // image compressor default config
    window.ctp.compressor = {
        quality: 0.8,
        maxWidth: 2000,
        maxHeight: 2000
    };

    // tinymce
    window.ctp.getTinyMceDefaultConfig = function( selector ) {
        return {
            selector: selector,
            skin: 'oxide',
            statusbar: false,
            plugins: [
                'advlist','autolink','lists','link','image','charmap',
                'fullscreen','insertdatetime','media','table','help','wordcount',
                'preview','anchor','searchreplace','visualblocks','textcolor'
            ],
            toolbar: 'undo redo | a11ycheck casechange blocks | bold italic backcolor forecolor | alignleft aligncenter alignright alignjustify | bullist numlist checklist outdent indent | removeformat | code table help'
        };
    }

    // initialize datatable
    window.ctp.utils.initializeDataTable = function( $el, customConfig ) {
        customConfig = ( typeof customConfig === 'undefined' ) ? {} : customConfig;
        let config = Object.assign( window.ctp.dataTableDefaultConfig, customConfig );
        return $el.DataTable( config );
    };

    // util function: populate options in a select tag
    window.ctp.utils.populateOptions = function( $select, optionArr, addMinusOption = false, addFirstOption = false ) {
        const emptyOptionStr = '<option value="0">Please select</option>';
        let htmlStr = '';
        if ( addFirstOption ) {
            htmlStr += emptyOptionStr;
        }
        for ( let i = 0; i < optionArr.length; i++ ) {
            htmlStr += `<option value="${ optionArr[i].id }">${ optionArr[i].title }</option>`;
        }
        if ( addMinusOption ) {
            htmlStr += '<option value="-1">Other (Please specify)</option>';
        }
        $select.html( htmlStr );
    };

    window.ctp.utils.populateMachineOptions = function( $select, optionArr,addFirstOption = false ) {
        const emptyOptionStr = '<option value="">Please select</option>';
        let htmlStr = '';
        if ( addFirstOption ) {
            htmlStr += emptyOptionStr;
        }
        for ( let i = 0; i < optionArr.length; i++ ) {
            htmlStr += `<option value="${ optionArr[i].machineId }">${ optionArr[i].machineName}</option>`;
        }
        $select.html( htmlStr );
    };

    // populate system users
    window.ctp.utils.populateUserOptions = function( $select, optionArr, addFirstOption = false ) {
        const emptyOptionStr = '<option value="">Please select</option>';
        let htmlStr = '';
        if ( addFirstOption ) {
            htmlStr += emptyOptionStr;
        }
        for ( let i = 0; i < optionArr.length; i++ ) {
            htmlStr += `<option value="${ optionArr[i].username }">${ optionArr[i].username }</option>`;
        }
        $select.html( htmlStr );
    };

    // populate location options
    window.ctp.utils.populateLocationOptions = function( $select, optionArr, addFirstOption = false ) {
        const emptyOptionStr = '<option value="" data-empty-option>Please select</option>';
        let htmlStr = '';
        if ( addFirstOption ) {
            htmlStr += emptyOptionStr;
        }
        for ( let i = 0; i < optionArr.length; i++ ) {
            // data attributes
            let dataAttributes = "";
            // check for unit id
            if ( optionArr[i].hasOwnProperty( 'unitId' ) ) {
                dataAttributes += `data-unit-id=${optionArr[i]['unitId']} `;
            }
            // check for floor id
            if ( optionArr[i].hasOwnProperty( 'floorId' ) ) {
                dataAttributes += `data-floor-id=${optionArr[i]['floorId']} `;
            }
            // check for store id
            if ( optionArr[i].hasOwnProperty( 'storeId' ) ) {
                dataAttributes += `data-store-id=${optionArr[i]['storeId']} `;
            }
            htmlStr += `<option value="${ optionArr[i].id }" ${dataAttributes}>${ optionArr[i].title }</option>`;
        }
        $select.html( htmlStr );
    };

    window.ctp.utils.populateTypeOptions = function( $select, optionArr, addMinusOption = false, addFirstOption = false ) {
        const emptyOptionStr = '<option value="">Please select</option>';
        let htmlStr = '';
        if ( addFirstOption ) {
            htmlStr += emptyOptionStr;
        }
        for ( let i = 0; i < optionArr.length; i++ ) {
            htmlStr += `<option value="${ optionArr[i].id }">${ optionArr[i].type }</option>`;
        }
        if ( addMinusOption ) {
            htmlStr += '<option value="-1">Other (Please specify)</option>';
        }
        $select.html( htmlStr );
    };

    // function to convert image to blob
    window.ctp.utils.fileToBase64 = function( file ) {
        return new Promise(function( resolve, reject ) {
            const reader=  new FileReader();
            reader.readAsDataURL( file );
            reader.onload = function() {
                resolve( reader.result );
            };
            reader.onerror = function( err ) {
                reject( err );
            }
        });
    };

    // debounce function
    window.ctp.utils.debounce = async function( func, interval ) {
        let lastCall = -1;
        return async function() {
            clearTimeout( lastCall );
            let args = arguments;
            let self = this;
            lastCall = setTimeout(function() {
                func.apply( self, args );
            }, interval );
        };
    };

    // check duplicates
    window.ctp.utils.hasDuplicates = function( $selector, $otherSelectors, value ) {
        let hasDuplicate = false;
        $otherSelectors.not( $selector ).each(function() {
            if ( $( this ).val() === value ) {
                hasDuplicate = true;
            }
        });
        return hasDuplicate;
    };

    // number with commas
    window.ctp.utils.numberWithCommas = function( number ) {
        let floatNumber = parseFloat( number ).toFixed( 2 );
        return floatNumber.toString().replace( /\B(?=(\d{3})+(?!\d))/g, "," );
    };
    
    // number with commas without fixed decimal places
    window.ctp.utils.numberWithCommasWithoutFixedDecimals = function( number ) {
        let floatNumber = parseFloat( number );
        return floatNumber.toString().replace( /\B(?=(\d{3})+(?!\d))/g, "," );
    };

    // format currency
    window.ctp.utils.formatCurrency = function( number, language, location, currency ) {
        let locale = language + '-' + location;
        return new Intl.NumberFormat( locale, { style: 'currency', currency: currency }).format( number );
    };

    // format currency pkr
    window.ctp.utils.formatCurrencyPKR = function( number ) {
        let locale = 'en-PK';
        return new Intl.NumberFormat( locale, { style: 'currency', currency: 'PKR' }).format( number );
    };
    
    window.ctp.utils.formatCurrencyPKRWithoutDecimal = function( number ) {
        return ( window.ctp.utils.formatCurrencyPKR( number ) ).split( '.' )[0];
    };
    
    //format decimal places of given decimal number upto given decimal places
    window.ctp.utils.formatDecimalPlaces = function( num, decimalPlaces ) {
        const strNum = num.toString();
        const decimalIndex = strNum.indexOf('.');

        if( decimalIndex === -1 ) {
            return num;
        }

        const endIndex = decimalIndex + decimalPlaces;
        const strFormattedNumber = strNum.slice( 0, endIndex + 1 );
        return parseFloat( strFormattedNumber );
    };

    // number to american system
    window.ctp.utils.numberToAmericanSystemWords = function( number ) {
        number = Math.ceil( number );
        const ones = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];
        const tens = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'];
        const teens = ['ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'];

        function convert_millions( num ) {
            if ( num >= 1000000 ) {
                return convert_millions( Math.floor( num / 1000000 ) ) + " million " + convert_thousands( num % 1000000 );
            } else {
                return convert_thousands( num );
            }
        }

        function convert_thousands( num ) {
            if ( num >= 1000 ) {
                return convert_hundreds( Math.floor( num / 1000 ) ) + " thousand " + convert_hundreds( num % 1000 );
            } else {
                return convert_hundreds( num );
            }
        }

        function convert_hundreds( num ) {
            if ( num > 99 ) {
                return ones[Math.floor( num / 100 )] + " hundred " + convert_tens( num % 100 );
            } else {
                return convert_tens( num );
            }
        }

        function convert_tens( num ) {
            if ( num < 10 ) return ones[num];
            else if ( num >= 10 && num < 20 ) return teens[num - 10];
            else {
                return tens[Math.floor( num / 10 )] + " " + ones[num % 10];
            }
        }

        function convert( num ) {
            if ( num == 0 ) return "zero";
            else return convert_millions( num );
        }

        let result = convert( number );
        if ( typeof result === 'undefined' ) {
            return zero;
        }
        return result.trim();
    };

    // number to south asian system
    window.ctp.utils.numberToSouthAsianSystemWords = function( number ) {
        const a = ['', 'one ', 'two ', 'three ', 'four ', 'five ', 'six ', 'seven ', 'eight ', 'nine ', 'ten ', 'eleven ', 'twelve ', 'thirteen ', 'fourteen ', 'fifteen ', 'sixteen ', 'seventeen ', 'eighteen ', 'nineteen '];
        const b = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'];
        const regex = /^(\d{2})(\d{2})(\d{2})(\d{1})(\d{2})$/;
        const getLT20 = ( n ) => a[Number( n )];
        const get20Plus = ( n ) => b[n[0]] + ' ' + a[n[1]];

        function convert ( input ) {
            const num = Number( input );
            if ( isNaN( num ) ) return '';
            if ( num === 0 ) return 'zero';
            const numStr = num.toString();
            if ( numStr.length > 9 ) {
                throw new Error( 'overflow' ) // Does not support converting more than 9 digits yet
            }
            const [, n1, n2, n3, n4, n5] = ( '000000000' + numStr ).substr( -9 ).match( regex ) // left pad zeros
            let str = '';
            str += n1 != 0 ? ( getLT20( n1 ) || get20Plus( n1 ) ) + 'crore ' : '';
            str += n2 != 0 ? ( getLT20( n2 ) || get20Plus( n2 ) ) + 'lakh ' : '';
            str += n3 != 0 ? ( getLT20( n3 ) || get20Plus( n3 ) ) + 'thousand ' : '';
            str += n4 != 0 ? getLT20( n4 ) + 'hundred ' : '';
            str += n5 != 0 && str != '' ? 'and ' : '';
            str += n5 != 0 ? ( getLT20( n5 ) || get20Plus( n5 ) ) : '';
            return str.trim();
        }

        return convert( number );
    };

    /**
     * sleep method
     */
    window.ctp.utils.sleep = async function( ms ) {
        return new Promise( resolve => setTimeout( resolve, ms ) );
    };

    // number to words
    window.ctp.utils.numberToWords = function( number, currencySymbol ) {
        let words = window.ctp.utils.numberToAmericanSystemWords( number );
        return words + ' only.';
    };

    // format grades
    window.ctp.utils.formatGrades = function( grades ) {
        let gradesArr = [];
        for ( let grade of grades ) {
            let gradeFormatted = {};
            gradeFormatted['id'] = grade.id;
            gradeFormatted['title'] = grade.type + '-' + grade.level + " (" + grade.description + ")";
            gradesArr.push( gradeFormatted );
        }
        return gradesArr;
    };

    // format companies
    window.ctp.utils.formatCompanies = function( companies ) {
        let companyArr = [];
        for ( let company of companies ) {
            let companyFormatted = {};
            companyFormatted['id'] = company.id;
            companyFormatted['title'] = company.title;
            companyArr.push( companyFormatted );
        }
        return companyArr;
    };

    // format raw expense data into pie chart data
    window.ctp.utils.formatExpenseDataForPieChart = function( expenses ) {
        let formattedData = [];
        for ( let expense of expenses ) {
            let formattedExpense = {};
            formattedExpense['name'] = expense.title;
            formattedExpense['y'] = expense.cost;
            formattedData.push( formattedExpense );
        }
        return formattedData;
    };

    // bind cost center search
     window.ctp.utils.bindItemNewCostCenterSearchAutoComplete = function( $selector ) {
        $selector.autocomplete({
            source: '/uind/rest/accounts-finance/cost-centers/search?departmentId=0',
            minLength: 1,
            select: async function (e, ui) {
                e.preventDefault();
                //format string
                let splitStr = ui.item.heirarchicalString.split(">");
                let hierarchyStr = `${splitStr[3]} > ${splitStr[2]} > ${splitStr[1]} > ${splitStr[0]}`
                let $container = $selector.closest( '[data-cost-center-search-container]' );
                $container.find( '[data-cost-center-one-search-id]' ).val( ui.item.oneId );
                $container.find( '[data-cost-center-two-search-id]' ).val( ui.item.twoId );
                $container.find( '[data-cost-center-three-search-id]' ).val( ui.item.threeId );
                $container.find( '[data-cost-center-four-search-id]' ).val( ui.item.fourId );
                $selector.val( hierarchyStr );
            },
            focus: function ( e, ui ) {
                $selector.val( '' );
            }
        }).data( 'ui-autocomplete' )._renderItem = function ( ul, ui ) {
            let splitStr = ui.heirarchicalString.split(">");
            let hierarchyStr = `${splitStr[3]} > ${splitStr[2]} > ${splitStr[1]} > ${splitStr[0]}`
            return $( '<li>' )
                .attr( "data-value", ui.costCenterOneId )
                .append( `<div class="ui-menu-item-wrapper">${hierarchyStr}</div>` )
                .appendTo( ul );
        };
    };


    // bind cost center search
     window.ctp.utils.bindItemCostCenterSearchAutoComplete = function( $selector ) {
        $selector.autocomplete({
            source: '/uind/rest/uic/cost-centers/search',
            minLength: 1,
            select: async function (e, ui) {
                e.preventDefault();
                let $container = $selector.closest( '[data-cost-center-search-container]' );
                $container.find( '[data-cost-center-one-search-id]' ).val( ui.item.costCenterOneId );
                $container.find( '[data-cost-center-two-search-id]' ).val( ui.item.costCenterTwoId );
                $container.find( '[data-cost-center-three-search-id]' ).val( ui.item.costCenterThreeId );
                $container.find( '[data-cost-center-four-search-id]' ).val( ui.item.costCenterFourId );
                $selector.val( ui.item.hierarchyString );
            },
            focus: function ( e, ui ) {
                $selector.val( '' );
            }
        }).data( 'ui-autocomplete' )._renderItem = function ( ul, ui ) {
            return $( '<li>' )
                .attr( "data-value", ui.costCenterOneId )
                .append( `<div class="ui-menu-item-wrapper">${ui.hierarchyString}</div>` )
                .appendTo( ul );
        };
    };

     // bind item brand search
    window.ctp.utils.bindItemBrandSearchAutoComplete = function( $selector ) {
        $selector.autocomplete({
            source: '/uind/rest/admin/item-brands/search',
            minLength: 1,
            select: async function (e, ui) {
                e.preventDefault();
                let $container = $selector.closest( '[data-item-brand-search-container]' );
                $container.find( '[data-item-brand-search-id]' ).val( ui.item.id );
                $selector.val( ui.item.title );
            },
            focus: function ( e, ui ) {
                $selector.val( '' );
                $selector.closest( '[data-item-brand-search-container]' ).find( '[data-item-brand-search-id]' ).removeAttr( 'value' );
            }
        }).data( 'ui-autocomplete' )._renderItem = function ( ul, ui ) {
            return $( '<li>' )
                .attr( "data-value", ui.id )
                .append( `<div class="ui-menu-item-wrapper">${ui.title}</div>` )
                .appendTo( ul );
        };
    };

    // bind item config search
    window.ctp.utils.bindItemConfigSearchAutoComplete = function( $selector ) {
        $selector.autocomplete({
            source: '/uind/rest/admin/item-brands/search',
            minLength: 1,
            select: async function (e, ui) {
                e.preventDefault();
                let $container = $selector.closest( '[data-item-config-search-container]' );
                $container.find( '[data-item-config-search-id]' ).val( ui.item.id );
                $selector.val( ui.item.title );
            },
            focus: function ( e, ui ) {
                $selector.val( '' );
                $selector.closest( '[data-item-config-search-container]' ).find( '[data-item-config-search-id]' ).removeAttr( 'value' );
            }
        }).data( 'ui-autocomplete' )._renderItem = function ( ul, ui ) {
            return $( '<li>' )
                .attr( "data-value", ui.id )
                .append( `<div class="ui-menu-item-wrapper">${ui.title}</div>` )
                .appendTo( ul );
        };
    };

    // format events data
    window.ctp.utils.formatLogbackEventsDataTimeSeries = function( events ) {
        let formattedData = [];
        for ( let event of events ) {
            let eventData = [];
            eventData.push( event.timestmp );
            eventData.push( 1 );
            formattedData.push( eventData );
        }
        return formattedData;
    };

    // get formatted date from date string
    // e.g. accept '2020-10-15' and return 'October 15, 2020'
    window.ctp.utils.getFormattedDateStringFromDateString = function( dateString ) {
        let date = new Date( Date.parse( dateString ) );
        return `${monthNames[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`;
    };

    // get formatted datetime from datetime string
    // e.g. accept '2021-01-25T12:38:02' and return '1/25/21 12:38 PM'
    window.ctp.utils.getFormattedDateTimeStringFromDateTimeString = function( dateString ) {
        if ( ! dateString ) {
            return null;
        }
        let date = new Date( Date.parse( dateString ) );
        let day = date.getDate();
        let month = date.getMonth();
        let year = date.getFullYear().toString().substr( -2 );
        let hour = ( date.getHours() > 12 ) ? ( date.getHours() - 12 ) : date.getHours();
        hour = ( hour > 9 ) ? hour : ( '0' + hour );
        let minute = date.getMinutes();
        minute = ( minute > 9 ) ? minute : ( '0' + minute );
        let ampm = ( hour >= 12 ) ? 'PM' : 'AM';
        return `${month + 1}/${day}/${year} ${hour}:${minute} ${ampm}`;
    };

    // get formatted datetime from datetime string
    // e.g. accept '2021-01-25T12:38:02' and return '1/25/21 12:38 PM'
    window.ctp.utils.getFormatted24HrsDateTimeStringFromDateTimeString = function( dateString ) {
        if ( ! dateString ) {
            return null;
        }
        let date = new Date( Date.parse( dateString ) );
        let day = date.getDate();
        day = ( day > 9 ) ? day : ( '0' + day );
        let month = date.getMonth();
        month = ( month > 9 ) ? month : ( '0' + month );
        let year = date.getFullYear();
        let hour = ( date.getHours() > 12 ) ? ( date.getHours() - 12 ) : date.getHours();
        hour = ( hour > 9 ) ? hour : ( '0' + hour );
        let minute = date.getMinutes();
        minute = ( minute > 9 ) ? minute : ( '0' + minute );
        let seconds = date.getSeconds();
        seconds = ( seconds > 9 ) ? seconds : ( '0' + seconds );
        return `${year}-${month + 1}-${day} ${hour}:${minute}:${seconds}`;
    };

    // get formatted datetime from datetime string
    // e.g. accept '2021-01-25T12:38:02' and return '12:38 PM'
    window.ctp.utils.getFormattedTimeStringFromDateTimeString = function( dateString ) {
        if ( ! dateString ) {
            return null;
        }
        let date = new Date( Date.parse( dateString ) );
        let hour = ( date.getHours() > 12 ) ? ( date.getHours() - 12 ) : date.getHours();
        hour = ( hour > 9 ) ? hour : ( '0' + hour );
        let minute = date.getMinutes();
        minute = ( minute > 9 ) ? minute : ( '0' + minute );
        let ampm = ( date.getHours() >= 12 ) ? 'PM' : 'AM';
        return `${hour}:${minute} ${ampm}`;
    };

    // get formatted date as yyyy-mm-dd
    window.ctp.utils.getFormattedDateAsISO8601 = function( date ) {
        let month = '' + ( date.getMonth() + 1 ),
            day = '' + date.getDate(),
            year = date.getFullYear();
        if ( month.length < 2 ) {
            month = '0' + month;
        }
        if ( day.length < 2 ) {
            day = '0' + day;
        }
        return [year, month, day].join( '-' );
    };

    // check if element in viewport
    window.ctp.utils.elementInViewport = function( el ) {
        // Special bonus for those using jQuery
        if ( typeof jQuery === "function" && el instanceof jQuery ) {
            el = el[0];
        }
        let rect = el.getBoundingClientRect();
        return (
            rect.top >= 0 &&
            rect.left >= 0 &&
            rect.bottom <= ( window.innerHeight || document.documentElement.clientHeight ) && /* or $(window).height() */
            rect.right <= ( window.innerWidth || document.documentElement.clientWidth ) /* or $(window).width() */
        );
    };
	
    // get number of number-of-months-added date
    window.ctp.utils.addMonthsToDate = function( date, numberOfMonths ) {
    	// make new date from given date
    	let newDate = new Date( date.getTime() );
    	// add given number of months to new date
    	const addedMonths = newDate.getMonth() + numberOfMonths;
    	newDate.setMonth( addedMonths );
    	
    	return newDate;
    }
    
    // get year-month string from date
    window.ctp.utils.getYearMonthStringFromDate = function( date ) {
    	let month = '' + ( date.getMonth() + 1 ); 
    	const year = date.getFullYear();
    	
    	if ( month.length < 2 ) {
       		month = '0' + month;
       	}
    	
    	return year + '-' + month;
    }
    
    // get date from year-month string
    window.ctp.utils.getDateFromYearMonthString = function( yearMonthString ) {
    	// split given year-month string in array
    	let yearMonthArray = yearMonthString.split( '-' );
    	const year = yearMonthArray[0];
    	const month = '' + ( parseInt( yearMonthArray[1] ) - 1 )
    	return new Date( year, month );
    }
    
    // get months added year-month string
    window.ctp.utils.getMonthsAddedYearMonthStringFromYearMonthString = function( startYearMonthString, numberOfMonths ) {
    	// get start date from startYearMonthString
    	const startDate = window.ctp.utils.getDateFromYearMonthString( startYearMonthString );
    	// get end date
		const endDate = window.ctp.utils.addMonthsToDate( startDate, numberOfMonths );
		// get end year-month string
		const endYearMonthString = window.ctp.utils.getYearMonthStringFromDate( endDate );
		
		return endYearMonthString;
    }
    
    // get months added year-month string from date
	window.ctp.utils.getMonthsAddedYearMonthStringFromDate = function( startDate, numberOfMonths ) {
		const endDate =  window.ctp.utils.addMonthsToDate( startDate, numberOfMonths );
		const endYearMonthString =  window.ctp.utils.getYearMonthStringFromDate( endDate );
		return endYearMonthString;
	}
	
	// get number of days in given date's month
	window.ctp.utils.getNumberOfDaysInGivenDate = function( date ) {
		return new Date( date.getFullYear(), date.getMonth() + 1, 0 ).getDate();
	}
	
	// get date object from date string ( string format yyyy-MM-dd )
	window.ctp.utils.getDateFromString = function( dateString ) {
		const dateArray = dateString.split( '-' );
		return new Date( dateArray[0], ( parseInt( dateArray[1] ) - 1 ) + '', dateArray[2] );
	}
	
	// add days to given date string ( string format yyyy-MM-dd )
	window.ctp.utils.addDaysToDate = function( dateString, days ) {
		const date = window.ctp.utils.getDateFromString( dateString );
		const daysAdded = date.getDate() + days;
		const month = date.getMonth();
		return new Date( date.getFullYear().toString(), month.toString(), daysAdded.toString() );
	}
	
    // google map styles
    window.ctp.google_map_styles = [
        {
            "featureType": "road",
            "elementType": "geometry",
            "stylers": [
                {
                    "lightness": 100
                },
                {
                    "visibility": "simplified"
                }
            ]
        },
        {
            "featureType": "water",
            "elementType": "geometry",
            "stylers": [
                {
                    "visibility": "on"
                },
                {
                    "color": "#C6E2FF"
                }
            ]
        },
        {
            "featureType": "poi",
            "elementType": "geometry.fill",
            "stylers": [
                {
                    "color": "#C5E3BF"
                }
            ]
        },
        {
            "featureType": "road",
            "elementType": "geometry.fill",
            "stylers": [
                {
                    "color": "#D1D1B8"
                }
            ]
        }
    ];

    // default file icon
    window.ctp.defaultFileIcon = '/uind/img/file_types/misc.svg';

    /**
     * get similarity score using Levenshtein distance algorithm
     */
    window.ctp.utils.getSimilarityScore = function( toCheck, original ) {
        const m = toCheck.length;
        const n = original.length;
        const d = [];
        for ( let i = 0; i <= m; i++ ) {
            d[i] = [i];
        }
        for ( let j = 0; j <= n; j++ ) {
            d[0][j] = j;
        }
        for ( let j = 1; j <= n; j++ ) {
            for ( let i = 1; i <= m; i++ ) {
                if ( toCheck[i - 1] === original[j - 1] ) {
                    d[i][j] = d[i - 1][j - 1];
                } else {
                    d[i][j] = Math.min(
                        d[i - 1][j] + 1, // deletion
                        d[i][j - 1] + 1, // insertion
                        d[i - 1][j - 1] + 1 // substitution
                    );
                }
            }
        }
        return Math.max( 0, Math.min( 1, ( 1 - d[m][n] / Math.max( m, n ) ) ) );
    };

    /*
    *  return the uuid for browser
    * */
    window.ctp.utils.getOrGenerateBrowserId = function () {
        let browserId = localStorage.getItem('device-hash');

        if ( !browserId )  {
            browserId =  window.ctp.utils.generateUUID();
            localStorage.setItem( 'device-hash', browserId );
        }

        return browserId;
    }

    /*
    *  generate uuid for each browser
    * */
    window.ctp.utils.generateUUID = function () {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
            var r = Math.random() * 16 | 0,
                v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    }

})();