00001 using System;
00002 using System.Collections.Generic;
00003 using System.Text;
00004 using Foodolini.Database;
00005
00006 namespace Foodolini.BusinessLogic
00007 {
00011 public partial class Recipe
00012 {
00016 private RecipeRow row;
00017
00021 private bool modified = true;
00022
00029 private Recipe(RecipeRow row)
00030 {
00031 this.row = row;
00032 this.modified = false;
00033 }
00034
00035 public Recipe(string title)
00036 {
00037 this.row = new RecipeRow();
00038 this.row.Title = title;
00039 this.modified = true;
00040 }
00041
00042 public Recipe()
00043 {
00044 this.row = new RecipeRow();
00045 this.modified = false;
00046 }
00047
00051 public string Title
00052 {
00053 get { return this.row.Title; }
00054 set
00055 {
00056 this.modified = true;
00057 this.row.Title = value;
00058 }
00059 }
00060
00061 internal long Id { get { return this.row.RecipeRowId; } }
00062
00063 private IDictionary<Ingredient, double> ingredients = null;
00064
00068 public IDictionary<Ingredient, double> Ingredients
00069 {
00070
00071 get
00072 {
00073 if (this.ingredients == null) {
00074 this.ingredients = new Dictionary<Ingredient, double>();
00075 if (this.row.RecipeRowId != 0) {
00076 var query = Settings.Repo.Where<RecipeFoodMapping>("RecipeId = @0", this.row.RecipeRowId);
00077 foreach (var row in query)
00078 this.ingredients.Add(new Ingredient(row.FoodDescriptionId), row.Quantity);
00079 }
00080 }
00081 return this.ingredients;
00082 }
00083 set
00084 {
00085 this.ingredients = value;
00086 }
00087 }
00088
00089 public void ClearIngredients()
00090 {
00091 this.ingredients.Clear();
00092 Settings.Repo.DeleteWhere<RecipeFoodMapping>("RecipeId = @0", this.row.RecipeRowId);
00093 }
00094
00095
00096 public void UpdateCategories()
00097 {
00098 this.tags.Clear();
00099 this.LoadCachedTags();
00100 this.tags = new List<string>();
00101 foreach (var tag in this.cachedTags)
00102 this.tags.Add(tag.Title);
00103
00104 }
00105
00109 private List<string> directions;
00110
00115 private List<RecipeStep> cachedSteps = null;
00116
00120 private void LoadSteps()
00121 {
00122 this.cachedSteps = new List<RecipeStep>();
00123 if (this.row.RecipeRowId != 0) {
00124 var query = Settings.Repo.Where<RecipeStep>("recipeid = @0 order by sortorder", this.row.RecipeRowId);
00125 foreach (var step in query)
00126 this.cachedSteps.Add(step);
00127 }
00128 }
00129
00133 public List<string> Directions
00134 {
00135 get
00136 {
00137
00138 if (this.directions == null) {
00139 this.directions = new List<string>();
00140 this.LoadSteps();
00141 foreach (var step in this.cachedSteps)
00142 this.directions.Add(step.Directions);
00143 }
00144 return directions;
00145 }
00146 set
00147 {
00148 this.directions = value;
00149 }
00150 }
00151
00155 public double AverageRating
00156 {
00157 get { return row.AverageRating.GetValueOrDefault(); }
00158 }
00159
00163 private RatingDictionary ratings = null;
00164
00169 public IDictionary<Person, double> Ratings
00170 {
00171 get
00172 {
00173 if (this.ratings == null)
00174 this.ratings = new RatingDictionary(this.row.RecipeRowId);
00175 return this.ratings;
00176 }
00177 }
00178
00184 public void Rate(Person user, double rating)
00185 {
00186 this.Ratings.Add(user, rating);
00187
00188 double temp = 0;
00189 foreach (var rat in this.ratings.Values) {
00190 temp += rat;
00191 Console.WriteLine(temp);
00192 }
00193
00194 if (this.ratings.Count == 0)
00195 this.row.AverageRating = temp;
00196 else
00197 this.row.AverageRating = temp / this.ratings.Count;
00198 this.modified = true;
00199 }
00200
00204 public Difficulty Difficulty
00205 {
00206 get { return (Difficulty)Enum.Parse(typeof(Difficulty), this.row.Difficulty.ToString()); }
00207 set
00208 {
00209 this.modified = true;
00210 this.row.Difficulty = (int)value;
00211 }
00212 }
00213
00217 public TimeSpan PreparationTime
00218 {
00219 get { return TimeSpan.FromTicks(this.row.PreparationTime.GetValueOrDefault()); }
00220 set
00221 {
00222 this.modified = true;
00223 this.row.PreparationTime = value.Ticks;
00224 }
00225 }
00226
00230 private List<RecipeTag> cachedTags = null;
00231
00235 private void LoadCachedTags()
00236 {
00237 this.cachedTags = new List<RecipeTag>();
00238 if (this.row.RecipeRowId != 0) {
00239 var query = Settings.Repo.Where<RecipeTag>("RecipeRowId = @0", this.row.RecipeRowId);
00240 foreach (var row in query)
00241 this.cachedTags.Add(row);
00242 }
00243 }
00244
00249 private IList<string> tags = null;
00250
00254 public IList<string> Categories
00255 {
00256
00257 get
00258 {
00259 if (this.tags == null) {
00260 this.LoadCachedTags();
00261 this.tags = new List<string>();
00262 foreach (var tag in this.cachedTags)
00263 this.tags.Add(tag.Title);
00264 }
00265 return this.tags;
00266 }
00267 set
00268 {
00269 this.tags = value;
00270 }
00271 }
00272
00276 private Picture picture = null;
00277
00281 private bool pictureModified = false;
00282
00287 public byte[] Picture
00288 {
00289 get
00290 {
00291 if (this.row.PictureId == 0)
00292 return null;
00293 if (this.picture == null) {
00294 this.picture = Settings.Repo.SingleWhere<Picture>("PictureId = @0", this.row.PictureId);
00295 if (this.picture == null) {
00296 this.row.PictureId = 0;
00297 this.modified = true;
00298 return null;
00299 }
00300 }
00301 return this.picture.Image;
00302 }
00303 set
00304 {
00305 this.pictureModified = true;
00306 if (this.picture == null)
00307 this.picture = new Picture();
00308 this.picture.Image = value;
00309 }
00310 }
00311
00315 public double Servings
00316 {
00317 get { return this.row.Servings; }
00318 set
00319 {
00320 this.modified = true;
00321 this.row.Servings = value;
00322 }
00323 }
00324
00328 public void Save()
00329 {
00330
00331 if (this.pictureModified) {
00332
00333 if (this.picture.PictureId != 0)
00334 Settings.Repo.Update<Picture>(this.picture);
00335 else
00336 Settings.Repo.Add<Picture>(this.picture);
00337
00338 this.pictureModified = false;
00339
00340 this.row.PictureId = this.picture.PictureId;
00341 }
00342
00343
00344 if (row.RecipeRowId == 0)
00345 Settings.Repo.Add(this.row);
00346 else if (this.modified)
00347 Settings.Repo.Update(this.row);
00348
00349
00350 if (this.directions != null) {
00351
00352 if (this.cachedSteps == null)
00353 this.LoadSteps();
00354
00355
00356 for (int i = 0; i < this.directions.Count; i++) {
00357
00358 if (i < this.cachedSteps.Count && this.cachedSteps[i].Directions != this.directions[i]) {
00359 this.cachedSteps[i].Directions = this.directions[i];
00360 Settings.Repo.Update<RecipeStep>(this.cachedSteps[i]);
00361
00362 } else if (i >= this.cachedSteps.Count) {
00363 RecipeStep step = new RecipeStep();
00364 step.Directions = this.directions[i];
00365 step.RecipeId = this.row.RecipeRowId;
00366 step.SortOrder = i;
00367 Settings.Repo.Add<RecipeStep>(step);
00368 this.cachedSteps.Add(step);
00369 }
00370 }
00371
00372 for (int i = this.directions.Count; i < this.cachedSteps.Count; i++)
00373 Settings.Repo.Delete<RecipeStep>(this.cachedSteps[i]);
00374
00375 if (this.directions.Count < this.cachedSteps.Count)
00376 this.cachedSteps.RemoveRange(this.directions.Count, this.cachedSteps.Count - this.directions.Count);
00377 }
00378
00379
00380 if (this.tags != null) {
00381
00382 if (this.cachedTags == null)
00383 this.LoadCachedTags();
00384
00385
00386 List<RecipeTag> deleted = new List<RecipeTag>();
00387 List<string> needNotAdd = new List<string>();
00388 foreach (var tag in this.cachedTags) {
00389
00390 if (!this.tags.Contains(tag.Title)) {
00391 Settings.Repo.Delete<RecipeTag>(tag);
00392 deleted.Add(tag);
00393
00394 } else
00395 needNotAdd.Add(tag.Title);
00396
00397 }
00398 this.cachedTags.RemoveAll(deleted.Contains);
00399
00400
00401 List<string> newTags = new List<string>(this.tags);
00402 newTags.RemoveAll(needNotAdd.Contains);
00403
00404 foreach (string tag in newTags) {
00405 RecipeTag t = new RecipeTag();
00406 t.RecipeRowId = this.row.RecipeRowId;
00407 t.Title = tag;
00408 Settings.Repo.Add<RecipeTag>(t);
00409 this.cachedTags.Add(t);
00410 }
00411 }
00412
00413
00414 if (this.ingredients != null) {
00415
00416 if (this.row.RecipeRowId != 0)
00417 Settings.Repo.DeleteWhere<RecipeFoodMapping>("RecipeId = @0", this.row.RecipeRowId);
00418 foreach (KeyValuePair<Ingredient, double> entry in this.ingredients) {
00419 RecipeFoodMapping map = new RecipeFoodMapping();
00420 entry.Key.Save();
00421 map.FoodDescriptionId = entry.Key.Id;
00422 map.RecipeId = this.row.RecipeRowId;
00423 map.Quantity = entry.Value;
00424 Settings.Repo.Add<RecipeFoodMapping>(map);
00425 }
00426 }
00427
00428
00429 if (this.ratings != null)
00430 this.ratings.Save(this.row.RecipeRowId);
00431
00432
00433 this.modified = false;
00434 }
00435
00441 internal static Recipe GetById(long recipeId)
00442 {
00443 return new Recipe(Settings.Repo.SingleWhere<RecipeRow>(" RecipeRowId = @0", recipeId));
00444 }
00445
00446 public FoodItem Cook(double servingsMultiplier)
00447 {
00448 return Cook(servingsMultiplier, 100);
00449 }
00450
00457 public FoodItem Cook(double servingsMultiplier, double percentage)
00458 {
00459 double totalCalories = 0;
00460 double totalCarbs = 0;
00461 double totalFat = 0;
00462 double totalProtein = 0;
00463 double totalWeight = 0;
00464
00465 List<FoodItem> inventory = new List<FoodItem>(FoodItem.ListFoodItems());
00466
00467 foreach (var item in this.Ingredients) {
00468
00469 double quantity = item.Value;
00470 Ingredient ingredient = item.Key;
00471
00472
00473 foreach (FoodItem fooditem in inventory) {
00474 if (Ingredient.Compare(fooditem.Ingredient, ingredient) & fooditem.ConsumedBy == null) {
00475 fooditem.Split(quantity * servingsMultiplier);
00476 fooditem.Save();
00477 break;
00478 }
00479 }
00480
00481
00482
00483 double calories = (ingredient.Nutrients[Nutrient.Calories] * quantity);
00484 double protein = (ingredient.Nutrients[Nutrient.Protein] * quantity);
00485 double carbs = (ingredient.Nutrients[Nutrient.Carbohydrates] * quantity);
00486 double fat = (ingredient.Nutrients[Nutrient.Fat] * quantity);
00487
00488
00489 totalCalories += calories;
00490 totalFat += fat;
00491 totalProtein += protein;
00492 totalCarbs += carbs;
00493
00494 totalWeight += item.Value;
00495 }
00496
00497
00498 double ingcount= this.Ingredients.Count;
00499 totalCalories /= ingcount;
00500 totalCalories /= 100.0;
00501 totalFat /= ingcount;
00502 totalFat /= 100.0;
00503 totalProtein /= ingcount;
00504 totalProtein /= 100;
00505 totalCarbs /= ingcount;
00506 totalCarbs /= 100.0;
00507 totalWeight *= (percentage / 100);
00508
00509
00510 Ingredient newIngredient = new Ingredient(this.Title);
00511
00512 Ingredient lookUp = Ingredient.GetByLongDescription(newIngredient.LongDescription);
00513
00514 if (lookUp != null) {
00515 newIngredient = lookUp;
00516
00517 newIngredient.Nutrients[Nutrient.Calories] = (double)totalCalories;
00518 newIngredient.Nutrients[Nutrient.Protein] = (double)totalProtein;
00519 newIngredient.Nutrients[Nutrient.Carbohydrates] = (double)totalCarbs;
00520 newIngredient.Nutrients[Nutrient.Fat] = (double)totalFat;
00521 } else {
00522
00523
00524 newIngredient.Nutrients.Add(Nutrient.Calories, (double)totalCalories);
00525 newIngredient.Nutrients.Add(Nutrient.Protein, (double)totalProtein);
00526 newIngredient.Nutrients.Add(Nutrient.Carbohydrates, (double)totalCarbs);
00527 newIngredient.Nutrients.Add(Nutrient.Fat, (double)totalFat);
00528
00529
00530
00531 newIngredient.Category = "Meals, Entrees, and Sidedishes";
00532 }
00533
00534 newIngredient.Save();
00535
00536
00537 FoodItem result = new FoodItem(totalWeight * servingsMultiplier, newIngredient, DateTime.Now.AddDays(2), true);
00538 return result;
00539 }
00540
00544 public void Delete()
00545 {
00546
00547 if (this.picture != null) {
00548
00549 this.picture.PictureId = 0;
00550
00551 this.pictureModified = true;
00552 }
00553
00554 if (this.row.PictureId != 0)
00555 Settings.Repo.DeleteWhere<Picture>("PictureId = @0", this.row.PictureId);
00556
00557
00558 if (this.cachedSteps == null)
00559 this.LoadSteps();
00560 foreach (var step in this.cachedSteps)
00561 Settings.Repo.Delete<RecipeStep>(step);
00562 this.cachedSteps.Clear();
00563
00564
00565 Settings.Repo.DeleteWhere<RecipeFoodMapping>("RecipeId = @0", this.row.RecipeRowId);
00566
00567
00568 if (this.cachedTags != null)
00569 this.cachedTags.Clear();
00570 Settings.Repo.DeleteWhere<RecipeTag>("RecipeRowId = @0", this.row.RecipeRowId);
00571
00572
00573 if (this.ratings == null)
00574 this.ratings = new RatingDictionary(this.row.RecipeRowId);
00575 this.ratings.Delete(this.row.RecipeRowId);
00576
00577
00578 if (this.row.RecipeRowId != 0)
00579 Settings.Repo.Delete<RecipeRow>(this.row);
00580
00581
00582 this.modified = true;
00583 }
00584
00585 private int searchPriority = 0;
00586
00592 public static List<Recipe> ListByCriteria(Criteria criteria)
00593 {
00594
00595 List<RecipeRow> recipeRows = new List<RecipeRow>();
00596 recipeRows = GetRecipeByMeal(criteria);
00597
00598
00599 List<Recipe> recipes = new List<Recipe>();
00600 foreach(RecipeRow row in recipeRows)
00601 recipes.Add(new Recipe(row));
00602
00603
00604 if(criteria.searchString.Count > 0)
00605 recipes = RemoveIrrelevantRecipes(recipes, criteria.searchString);
00606
00607
00608 if(criteria.expirationDate)
00609 recipes = PrioritiseRecipesAfterDate(recipes, criteria);
00610
00611
00612 if(criteria.rating)
00613 recipes = PrioritiseRecipesAfterRating(recipes, criteria);
00614
00615
00616 if(criteria.searchString.Count > 0)
00617 recipes = PrioritiseRecipes(recipes, criteria.searchString);
00618
00619
00620 recipes = SortRecipes(recipes, 0, recipes.Count-1);
00621
00622 recipes.Reverse();
00623
00624 return recipes;
00625 }
00626
00631 private static List<Recipe> PrioritiseRecipesAfterRating(List<Recipe> recipes, Criteria criteria)
00632 {
00633 List<Recipe> returnList = new List<Recipe>();
00634 returnList = recipes;
00635
00636 foreach(Recipe recipe in returnList)
00637 {
00638 recipe.searchPriority += GetRatingPriority(recipe.AverageRating) * criteria.ratingPriority;
00639 }
00640
00641 return returnList;
00642 }
00643
00647 private static int GetRatingPriority(double rating)
00648 {
00649
00650 if(rating == 0.0 || rating == 3.0)
00651 return 0;
00652
00653 if(rating < 3)
00654 {
00655 if(rating < 2)
00656 {
00657 if(rating < 1.5)
00658 return -4;
00659 else
00660 return -3;
00661 }
00662 else
00663 {
00664 if(rating < 2.5)
00665 return -2;
00666 else
00667 return -1;
00668 }
00669 }
00670 else
00671 {
00672 if(rating < 4)
00673 {
00674 if(rating < 3.5)
00675 return 1;
00676 else
00677 return 2;
00678 }
00679 else
00680 {
00681 if(rating < 4.5)
00682 return 3;
00683 else
00684 return 4;
00685 }
00686 }
00687 }
00688
00693 private static List<Recipe> PrioritiseRecipesAfterDate(List<Recipe> recipes, Criteria criteria)
00694 {
00695 List<Recipe> returnList = new List<Recipe>();
00696 returnList = recipes;
00697
00698
00699 foreach(Recipe recipe in returnList)
00700 {
00701
00702 foreach(var ingredient in recipe.Ingredients)
00703 {
00704 FoodItem item = ingredient.Key.InStorage();
00705 if(item != null)
00706 recipe.searchPriority += GetExpirationDatePriority(item.ExpirationDate) * criteria.expirationDatePriority;
00707 }
00708 }
00709 return returnList;
00710 }
00711
00715 private static int GetExpirationDatePriority(DateTime expirationDate)
00716 {
00717 TimeSpan timeTillExpires = expirationDate - DateTime.Now;
00718 switch((int)Math.Ceiling(timeTillExpires.TotalDays))
00719 {
00720 case 1:
00721 return 6;
00722 case 2:
00723 return 4;
00724 case 3:
00725 return 3;
00726 case 4:
00727 return 2;
00728 default:
00729 return 0;
00730 }
00731 }
00732
00738 private static List<Recipe> SortRecipes(List<Recipe> recipes, int index, int length)
00739 {
00740 if(index < length)
00741 {
00742 int q = Partition(ref recipes, index, length);
00743 SortRecipes(recipes, index, q - 1);
00744 SortRecipes(recipes, q + 1, length);
00745 }
00746
00747 return recipes;
00748 }
00749
00753 private static int Partition(ref List<Recipe> recipes, int start, int length)
00754 {
00755 Recipe recipe = recipes[length];
00756 int i = start-1;
00757 for(int j = start; j < length; j++)
00758 {
00759 if(recipes[j].searchPriority <= recipe.searchPriority)
00760 {
00761 i += 1;
00762 Recipe tmp = recipes[i];
00763 recipes[i] = recipes[j];
00764 recipes[j] = tmp;
00765 }
00766 }
00767 recipes[length] = recipes[i+1];
00768 recipes[i+1] = recipe;
00769
00770 return i+1;
00771 }
00772
00776 private static List<Recipe> PrioritiseRecipes(List<Recipe> recipes, List<string> keywords)
00777 {
00778 List<Recipe> returnList = new List<Recipe>();
00779 returnList = recipes;
00780
00781
00782 foreach(string keyword in keywords)
00783 {
00784 foreach(Recipe recipe in returnList)
00785 {
00786
00787 if(recipe.Title.Contains(keyword))
00788 recipe.searchPriority += 10;
00789
00790
00791 if(recipe.cachedTags == null)
00792 recipe.LoadCachedTags();
00793 {
00794 bool keywordPresent = false;
00795 foreach(RecipeTag tag in recipe.cachedTags)
00796 {
00797 if(tag.Title.Contains(keyword))
00798 {
00799 keywordPresent = true;
00800 break;
00801 }
00802 }
00803 if(keywordPresent)
00804 recipe.searchPriority += 7;
00805 }
00806
00807
00808 if(recipe.cachedSteps == null)
00809 recipe.LoadSteps();
00810 {
00811 bool keywordPresent = false;
00812 foreach(RecipeStep step in recipe.cachedSteps)
00813 {
00814 if(step.Directions.Contains(keyword))
00815 {
00816 keywordPresent = true;
00817 break;
00818 }
00819 }
00820 if(keywordPresent)
00821 recipe.searchPriority += 4;
00822 }
00823 }
00824 }
00825
00826 return returnList;
00827 }
00828
00832 private static List<Recipe> RemoveIrrelevantRecipes(List<Recipe> recipes, List<string> keywords)
00833 {
00834 List<Recipe> returnList = new List<Recipe>();
00835 returnList = recipes;
00836
00837
00838 foreach(string keyword in keywords)
00839 {
00840
00841 foreach(Recipe recipe in returnList)
00842 {
00843
00844 if(recipe.Title.ToLower().Contains(keyword.ToLower()))
00845 recipe.searchPriority += 1;
00846 else
00847 recipe.searchPriority -= 1;
00848
00849
00850
00851 if(recipe.cachedSteps == null)
00852 recipe.LoadSteps();
00853 {
00854 bool keywordPresent = false;
00855
00856 foreach(RecipeStep step in recipe.cachedSteps)
00857 {
00858 if(step.Directions.ToLower().Contains(keyword.ToLower()))
00859 {
00860 keywordPresent = true;
00861 break;
00862 }
00863 }
00864
00865
00866 if(keywordPresent)
00867 recipe.searchPriority += 1;
00868 else
00869 recipe.searchPriority -= 1;
00870 }
00871
00872
00873 {
00874
00875 if(recipe.cachedTags == null)
00876 recipe.LoadCachedTags();
00877
00878 bool keywordPresent = false;
00879
00880 foreach(RecipeTag tag in recipe.cachedTags)
00881 {
00882 if(tag.Title.ToLower().Contains(keyword.ToLower()))
00883 {
00884 keywordPresent = true;
00885 break;
00886 }
00887 }
00888
00889
00890 if(keywordPresent)
00891 recipe.searchPriority += 1;
00892 else
00893 recipe.searchPriority -= 1;
00894 }
00895 }
00896 }
00897
00898
00899
00900 for(int i = 0; i < returnList.Count; i++)
00901 {
00902 if(returnList[i].searchPriority == keywords.Count * (-3))
00903 {
00904 returnList.RemoveAt(i);
00905 i--;
00906 }
00907 else
00908 returnList[i].searchPriority = 0;
00909 }
00910
00911 return returnList;
00912 }
00913
00917 private static List<RecipeRow> GetRecipeByMeal(Criteria criteria)
00918 {
00919 List<RecipeRow> recipeRows = new List<RecipeRow>();
00920
00921 if(criteria.Meal == Meal.None)
00922 {
00923 var query = Settings.Repo.All<RecipeRow>();
00924 foreach(var row in query)
00925 recipeRows.Add(row);
00926 }
00927 else
00928 {
00929
00930 for(int i = 0; i < 4; i++)
00931 {
00932 if(((int)criteria.Meal & (int)Math.Pow(2, i)) != 0)
00933 {
00934 Meal type = (Meal)(int)Math.Pow(2,i);
00935 var tagQuery = Settings.Repo.Where<RecipeTag>("title = @0", type.ToString());
00936
00937
00938 foreach(var tag in tagQuery)
00939 {
00940 var rowQuery = Settings.Repo.SingleWhere<RecipeRow>("reciperowid = @0", tag.RecipeRowId);
00941 recipeRows.Add(rowQuery);
00942 }
00943 }
00944 }
00945 }
00946
00947 return recipeRows;
00948 }
00949
00955 public static Recipe GetByTitle(string title)
00956 {
00957 RecipeRow row = Settings.Repo.SingleWhere<RecipeRow>("Title = @0", title);
00958 if(row != null)
00959 return new Recipe(row);
00960 return null;
00961 }
00962
00963 public override string ToString()
00964 {
00965 return base.ToString() + " " + this.Title;
00966 }
00967 }
00968
00972 public enum Difficulty
00973 {
00974 Easy,
00975 Medium,
00976 Hard,
00977 Unknown
00978 }
00979 }