FoodNutritionSrvc.js

angular.module('homepage').
factory('FoodNutritionSrvc', function (NutritionDataJSON) {
    // Store list of foods with their nutrition details
    var FoodsDataStore = {
        foods:  [],
        drinks: []
    };

    /**
     * Calculate matchindex based on the number of words matched against the foodname passed.
     * @param  {Array} titles List of titles all describing one food title
     * @param  {String} foodname Title of food entered by user
     * @return {Object} bestmatch Best match matchindex and titleindex
     */
    // TODO This doesnt account for errorneous names entered by user
    // TODO This doesnt account for plural names
    function matchFoodByName(titles, foodname) {
        // Number of words matched against the foodname passed
        var matchindex = 0; // Stores the current iteration matchindex
        var bestmatch = {   // Stores info about the past best match
            matchindex: 0,
            titleindex: -1,
            lengthmatch: false
        };

        // match against all titles until matchindex and word count match for one title
        for(var index in titles) {
            var title = titles[index];
            var words = title.split(" ");
            var parts = foodname.split(" ");

            // match words
            for(var jndex in parts) {
                var part = parts[jndex];
                // TODO add polyfill added in ES6 though is an ES5 specification
                // Array.indexOf() case insensitive
                var isMatch = words.some(function(element) {
                    return part.toLowerCase() === element.toLowerCase();
                });
                if (isMatch) {
                    matchindex++;
                }
            }

            // store in bestmatch if better than last match
            if (bestmatch.matchindex < matchindex) {
                bestmatch.matchindex = matchindex;
                bestmatch.titleindex = parseInt(index);
            }
            // end search if matchindex and word count match
            if(matchindex === parts.length) {
                bestmatch.lengthmatch = true;
                break;
            }
        }

        return bestmatch;
    }

    /**
     * Match foodname with all items titles in the list
     * @param  {Array} list     [description]
     * @param  {String} foodname Title of food entered by user
     * @return {Object} bestmatch Best match matchindex and titleindex
     */
    function matchNames(list, foodname) {
        var bestmatch = {   // Stores info about the past best match
            matchindex: 0,
            titleindex: -1,
            lengthmatch: false
        };

        for(var index in list) {
            var titles = list[index].title;
            var matchinfo = matchFoodByName(titles, foodname);
            if(bestmatch.matchindex < matchinfo.matchindex) {
                bestmatch.matchindex = matchinfo.matchindex;
                bestmatch.titleindex = matchinfo.titleindex;
                bestmatch.lengthmatch = matchinfo.lengthmatch;
                if(bestmatch.lengthmatch === true) {
                    bestmatch.details = list[index];
                    break;
                }
            }
        }

        return bestmatch;
    }

    // Based on type array, look for foodname
    // If not found in that array, look for other array(s)
    function findMatch(foodtype, foodname) {
        var bestmatch = {   // Stores info about the past best match
            matchindex: 0,
            titleindex: -1
        };

        if(foodtype === 'FOOD') {
            bestmatch = matchNames(FoodsDataStore.foods, foodname);
            if (bestmatch.matchindex === 0) {
                bestmatch = matchNames(FoodsDataStore.drinks, foodname);
            }
        }
        else {
            bestmatch = matchNames(FoodsDataStore.drinks, foodname);
            if (bestmatch.matchindex === 0) {
                bestmatch = matchNames(FoodsDataStore.foods, foodname);
            }
        }

        return bestmatch;
    }

    /**
     * Return conversion index based on the type entered by user w.r.t type in JSON
     *  possible values:
	 * 	FOOD - PCE, TSP, BWL, ONC, GRM
	 * 	DRNK - CUP, GLS, BTL, PTR, TWR, LTR
     * @param  {String} jsontype Type from the json
     * @param  {String} usertype Type entered by the user
     * @return {Number} conversionindex conversion index
     */
    function getConversionIndex(jsontype, usertype) {
        var weightmap = {
            'PCE': 10,
            'TSP': 10,
            'BWL': 100,
            'ONC': 0.035,
            'GRM': 1,
            'CUP': 100,
            'GLS': 200,
            'BTL': 500,
            'PTR': 1890,
            'TWR': 3000,
            'LTR': 1000
        };

        var conversionIndex = weightmap[usertype] / weightmap[jsontype];

        return conversionIndex;
    }

    function calculateCalories(foodname, foodtype, quantity, qtytype) {
        // Based on type array, look for foodname
        // If not found in that array, look for other array(s)
        // If no match found return X
        var bestmatch = findMatch(foodtype, foodname);

        // Once match is found, check if quantity type matches
        // If it doesnt, use convertor to convert to nutrition type in json
        // If it matches, calculate total calories, based on amount entered by user
        if(bestmatch.matchindex > 0 && bestmatch.details !== undefined) {
            var conversionIndex = getConversionIndex(bestmatch.details.qty.type, qtytype);
            bestmatch.calories = conversionIndex * quantity * bestmatch.details.nutrition.calories;
        }
        else {
            bestmatch.calories = bestmatch.details === undefined ? 0 : bestmatch.details.nutrition.calories;
        }

        return bestmatch;
    }

    /**
     * Get Nutrition details from the food name
     * @param  {String} foodname [description]
     * @param  {String} foodtype [description]
     * @param  {Number} quantity [description]
     * @param  {String} qtytype  [description]
     * @return {Object} bestmatch object with bestmatch info and calorie count
     */
    function getNutritionInfo(foodname, foodtype, quantity, qtytype) {
        if(FoodsDataStore.foods.length === 0 || FoodsDataStore.drinks.length === 0) {
            FoodsDataStore.foods  = NutritionDataJSON.nutritionInfo.foods;
            FoodsDataStore.drinks = NutritionDataJSON.nutritionInfo.drinks;
        }

        if(foodname === undefined) {
            return { 'calories': 0 };
        }
        else {
            return calculateCalories(foodname, foodtype, quantity, qtytype);
        }
    }

    return {
        getNutritionInfo: getNutritionInfo
    };
}).
factory('NutritionDataJSON', function () {
    // Keeping JSON inline so that there is no asyn call to be made, as it is problematic for implementing filters that use this service on load
    // Stateful filter implementation is not changing data after first stable data, even though the filter is called with different params
    var nutritionInfo = {"foods":[{"title":["Egg","Eggs","Egg boiled"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"80"}},{"title":["Egg Poached","Eggs Poached"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"80"}},{"title":["Egg Fried","Eggs Fried","Egg Fry"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"110"}},{"title":["Egg Omelet","Omelet","Omlate","Omelette","Egg Omelette"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"120"}},{"title":["Bread slice","Bread"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"45"}},{"title":["Bread slice with butter"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"90"}},{"title":["Chapati"],"foodtype":{"type":"FOOD","region":"INDN"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"60"}},{"title":["Puri"],"foodtype":{"type":"FOOD","region":"INDN"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"75"}},{"title":["Paratha"],"foodtype":{"type":"FOOD","region":"INDN"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"150"}},{"title":["Veggie"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"BWL"},"nutrition":{"calories":"150"}},{"title":["Idli"],"foodtype":{"type":"FOOD","region":"INDN"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"100"}},{"title":["Dosa","Plain Dosa"],"foodtype":{"type":"FOOD","region":"INDN"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"120"}},{"title":["Masala Dosa"],"foodtype":{"type":"FOOD","region":"INDN"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"250"}},{"title":["Sambar","Sambhar","Dal"],"foodtype":{"type":"FOOD","region":"INDN"},"qty":{"amount":"1","type":"BWL"},"nutrition":{"calories":"150"}},{"title":["Rajma Dal"],"foodtype":{"type":"FOOD","region":"INDN"},"qty":{"amount":"1","type":"BWL"},"nutrition":{"calories":"140"}},{"title":["Rice","Plain Rice"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"BWL"},"nutrition":{"calories":"120"}},{"title":["Fried Rice"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"BWL"},"nutrition":{"calories":"150"}},{"title":["Nan"],"foodtype":{"type":"FOOD","region":"INDN"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"150"}},{"title":["Curd"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"BWL"},"nutrition":{"calories":"100"}},{"title":["Veg Curry","Vegetable Curry"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"BWL"},"nutrition":{"calories":""}},{"title":["Salad"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"BWL"},"nutrition":{"calories":"100"}},{"title":["Papad"],"foodtype":{"type":"FOOD","region":"INDN"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"45"}},{"title":["Cutlet"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"BWL"},"nutrition":{"calories":"75"}},{"title":["Pickle"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"15"}},{"title":["Gobi Manchurian"],"foodtype":{"type":"FOOD","region":"INDN"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"60"}},{"title":["Soup"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"BWL"},"nutrition":{"calories":"75"}},{"title":["Fruit Bowl","Fruit Salad","Fruits"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"BWL"},"nutrition":{"calories":"150"}},{"title":["Gulab Jamun"],"foodtype":{"type":"FOOD","region":"INDN"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"140"}},{"title":["Cucumber","Kheera","Kakhadi"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"8"}},{"title":["Carrot","Gajar"],"foodtype":{"type":"FOOD","region":"GLBL"},"qty":{"amount":"1","type":"PCE"},"nutrition":{"calories":"41"}}],"drinks":[{"title":["Tea"],"foodtype":{"type":"DRNK","region":"GLBL"},"qty":{"amount":"1","type":"CUP"},"nutrition":{"calories":"45"}},{"title":["Black Tea"],"foodtype":{"type":"DRNK","region":"GLBL"},"qty":{"amount":"1","type":"CUP"},"nutrition":{"calories":"15"},"description":"Without milk and sugar"},{"title":["Coffee"],"foodtype":{"type":"DRNK","region":"GLBL"},"qty":{"amount":"1","type":"CUP"},"nutrition":{"calories":"45"}},{"title":["Black Coffee"],"foodtype":{"type":"DRNK","region":"GLBL"},"qty":{"amount":"1","type":"CUP"},"nutrition":{"calories":"15"},"description":"Without milk and sugar"},{"title":["Milk"],"foodtype":{"type":"DRNK","region":"GLBL"},"qty":{"amount":"1","type":"GLS"},"nutrition":{"calories":"65"}},{"title":["Protein Milk","Protein Milk Shake"],"foodtype":{"type":"DRNK","region":"GLBL"},"qty":{"amount":"1","type":"CUP"},"nutrition":{"calories":"125"}},{"title":["Horlicks Milk"],"foodtype":{"type":"DRNK","region":"GLBL"},"qty":{"amount":"1","type":"GLS"},"nutrition":{"calories":"120"}},{"title":["Fruit Juice"],"foodtype":{"type":"DRNK","region":"GLBL"},"qty":{"amount":"1","type":"GLS"},"nutrition":{"calories":"120"}},{"title":["Soft Drink"],"foodtype":{"type":"DRNK","region":"INDN"},"qty":{"amount":"1","type":"GLS"},"nutrition":{"calories":"90"}},{"title":["Diet Coke"],"foodtype":{"type":"DRNK","region":"GLBL"},"qty":{"amount":"1","type":"GLS"},"nutrition":{"calories":"0"}},{"title":["Maaza"],"foodtype":{"type":"DRNK","region":"GLBL"},"qty":{"amount":"1","type":"CUP"},"nutrition":{"calories":"54"}},{"title":["Beer"],"foodtype":{"type":"DRNK","region":"GLBL"},"qty":{"amount":"1","type":"BTL"},"nutrition":{"calories":"200"}},{"title":["Soda"],"foodtype":{"type":"DRNK","region":"GLBL"},"qty":{"amount":"1","type":"BTL"},"nutrition":{"calories":"20"}},{"title":["Milk Shake"],"foodtype":{"type":"DRNK","region":"GLBL"},"qty":{"amount":"1","type":"GLS"},"nutrition":{"calories":"200"}},{"title":["Mosambi Juice","Sweet Lime Juice"],"foodtype":{"type":"DRNK","region":"GLBL"},"qty":{"amount":"1","type":"GLS"},"nutrition":{"calories":"100"}}]};

    return {
        nutritionInfo: nutritionInfo
    };
});