using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Linq.Expressions; using System.Text.RegularExpressions; using MileageTraker.Web.Models; using MileageTraker.Web.Utility; using MileageTraker.Web.ViewModels; namespace MileageTraker.Web.DAL { public class DataService : IDisposable { private readonly MileageTrakerContext _db = new MileageTrakerContext(); #region Log public void AddLog(Log log) { log.Created = DateTime.Now; _db.Logs.Add(log); _db.SaveChanges(); UpdateCurrentOdometer(log.VehicleId); } public IQueryable GetLogs() { return _db.Logs; } public Log GetLog(int id) { return _db.Logs.Find(id); } public IEnumerable GetValidLogYears() { return from l in GetLogs() group l by new {year = l.Date.Year} into y orderby y.Key descending select y.Key.year; } public IEnumerable GetValidLogMonths(int year) { var start = new DateTime(year, 1, 1); var end = new DateTime(year + 1, 1, 1); return from l in GetLogs() where l.Date >= start && l.Date < end group l by new { month = l.Date.Month } into m orderby m.Key descending select m.Key.month; } public IEnumerable GetLogIndexViewModels(IQueryable logs) { var t = from log in logs let previousLog = (from ol in _db.Logs where ol.VehicleId == log.VehicleId && ((ol.Date <= log.Date && ol.EndOdometer < log.EndOdometer) || (ol.EndOdometer == log.EndOdometer && ol.Created < log.Created)) orderby ol.EndOdometer descending, ol.Created descending select ol).FirstOrDefault() select new {log, previousLog}; return from l in t.ToList() select new LogIndexViewModel(l.log, l.previousLog); } public IEnumerable GetMonthlyVehicleMileageItems(LogQueryViewModel query) { var items = from l in from log in FilterLogs(_db.Logs, query) let previousLog = (from ol in _db.Logs where ol.VehicleId == log.VehicleId && ((ol.Date <= log.Date && ol.EndOdometer < log.EndOdometer) || (ol.EndOdometer == log.EndOdometer && ol.Created < log.Created)) orderby ol.EndOdometer descending, ol.Created descending select ol).FirstOrDefault() select new {log, previousLog, miles = (previousLog != null ? log.EndOdometer - previousLog.EndOdometer : 0)} group l by l.log.VehicleId into g join v in _db.Vehicles on g.Key equals v.VehicleId let miles = g.Sum(l => l.miles) let gasPurchased = g.Sum(l => l.log.GasPurchased) where miles > 0 || gasPurchased > 0 let logPairs = from i in g orderby i.log.Date descending , i.log.EndOdometer descending select new { PreviousLog = i.previousLog, Log = i.log } select new { VehicleId = g.Key, GasPurchased = gasPurchased, Miles = miles, v.Prog, LogPairs = logPairs }; return items.ToList().Select(i => new VehicleMileageItem { VehicleId = i.VehicleId, GasPurchased = i.GasPurchased, Miles = i.Miles, Prog = i.Prog, LogPairs = i.LogPairs.ToList() .Select(lp => new Tuple(lp.PreviousLog, lp.Log)) }); } public IEnumerable GetMonthlyEmployeeMileageItems(LogQueryViewModel query) { var items = from l in from log in FilterLogs(_db.Logs, query) let previousLog = (from ol in _db.Logs where ol.VehicleId == log.VehicleId && ((ol.Date <= log.Date && ol.EndOdometer < log.EndOdometer) || (ol.EndOdometer == log.EndOdometer && ol.Created < log.Created)) orderby ol.EndOdometer descending, ol.Created descending select ol).FirstOrDefault() select new { log, previousLog, miles = (previousLog != null ? log.EndOdometer - previousLog.EndOdometer : 0) } group l by l.log.EmployeeName into g let miles = g.Sum(l => l.miles) let gasPurchased = g.Sum(l => l.log.GasPurchased) where miles > 0 || gasPurchased > 0 let logPairs = from i in g orderby i.log.Date descending, i.log.EndOdometer descending select new { PreviousLog = i.previousLog, Log = i.log } select new { EmployeeName = g.Key, GasPurchased = gasPurchased, Miles = miles, LogPairs = logPairs }; return items.ToList().Select(i => new EmployeeMileageItem { EmployeeName = i.EmployeeName, GasPurchased = i.GasPurchased, Miles = i.Miles, LogPairs = i.LogPairs.ToList() .Select(lp => new Tuple(lp.PreviousLog, lp.Log)) }); } public static IQueryable FilterLogs(IQueryable logs, LogQueryViewModel query) { if (query.Year.HasValue & query.Month.HasValue) { var start = new DateTime(query.Year.Value, query.Month.Value, 1); var end = start.AddMonths(1); logs = logs.Where(l => l.Date >= start && l.Date < end); } if (query.LogType.HasValue) { // WTF The specified value is not an instance of type 'Edm.Int32' Expression> predicate; switch (query.LogType.Value) { case MileageLogType.NonCommuting: predicate = l => l.LogType.Value == 1; break; case MileageLogType.Commuting: predicate = l => l.LogType.Value == 2; break; case MileageLogType.GasPurchase: predicate = l => l.LogType.Value == 3; break; default: throw new ArgumentOutOfRangeException(); } logs = logs.Where(predicate); } return logs; } public Log GetPreviousLog(Log current) { var vehicleId = current.VehicleId; var date = current.Date; var endOdometer = current.EndOdometer; return GetPreviousLog(endOdometer, vehicleId, date, current.Created); } public Log GetPreviousLog(int endOdometer, string vehicleId, DateTime date, DateTime created) { return GetPreviousLogQuery(endOdometer, vehicleId, date, created).FirstOrDefault(); } private IOrderedQueryable GetPreviousLogQuery(int endOdometer, string vehicleId, DateTime date, DateTime created) { return from pl in _db.Logs where pl.VehicleId == vehicleId && ((pl.Date <= date && pl.EndOdometer < endOdometer) || (pl.EndOdometer == endOdometer && pl.Created < created)) orderby pl.EndOdometer descending, pl.Created descending select pl; } public Log GetNextLog(Log current) { var vehicleId = current.VehicleId; var date = current.Date; var endOdometer = current.EndOdometer; return GetNextLog(endOdometer, vehicleId, date, current.Created); } public Log GetNextLog(int endOdometer, string vehicleId, DateTime date, DateTime created) { return (from nl in GetLogs() where nl.VehicleId == vehicleId && ((nl.Date >= date && nl.EndOdometer > endOdometer) || (nl.EndOdometer == endOdometer && nl.Created > created)) orderby nl.EndOdometer ascending, nl.Created ascending select nl).FirstOrDefault(); } public void UpdateLog(Log log) { var currentLog = GetLog(log.LogId); // ignore these properties while updating log.Created = currentLog.Created; log.Source = currentLog.Source; log.UserAgent = currentLog.UserAgent; log.UserHostAddress = currentLog.UserHostAddress; _db.Entry(currentLog).CurrentValues.SetValues(log); _db.Entry(currentLog).State = EntityState.Modified; _db.SaveChanges(); UpdateCurrentOdometer(log.VehicleId); } public void DeleteLog(int id) { var log = _db.Logs.Find(id); _db.Logs.Remove(log); _db.SaveChanges(); UpdateCurrentOdometer(log.VehicleId); } public IEnumerable GetRecentLogsByEmployee(string employeeName) { return (from l in _db.Logs where l.EmployeeName == employeeName orderby l.Created descending select l) .Take(3) .ToList(); } #endregion #region Vehicle public void AddVehicle(Vehicle vehicle) { if (vehicle.Assigned == "Unassigned") vehicle.Assigned = null; _db.Vehicles.Add(vehicle); _db.SaveChanges(); } public IEnumerable GetVehicles() { var vehicles = _db.Vehicles; return vehicles; } public Vehicle GetVehicle(string id) { return _db.Vehicles.Find(id); } public void UpdateVehicle(Vehicle vehicle) { if (vehicle.Assigned == "Unassigned") vehicle.Assigned = null; _db.Entry(vehicle).State = EntityState.Modified; _db.SaveChanges(); } public void UpdateCurrentOdometer(string vehicleId) { var mostRecentOdometerQuery = from log in _db.Logs where log.VehicleId == vehicleId orderby log.Date descending, log.EndOdometer descending select log; var mostRecentOdometer = mostRecentOdometerQuery.FirstOrDefault(); var vehicle = _db.Vehicles.Find(vehicleId); if (mostRecentOdometer != null && mostRecentOdometer.EndOdometer != vehicle.CurrentOdometer) { vehicle.CurrentOdometer = mostRecentOdometer.EndOdometer; UpdateVehicle(vehicle); } } public void ValidateOdometerChronology(string vehicleId, int odometer, DateTime date) { var conflictingLog = GetFirstConflictingOdometerChronologyLog(vehicleId, date, odometer); if (conflictingLog != null) { throw new ChronologicalOrderException( String.Format( "Odometer of {0} miles on {1:d} conflicts with other logs for vehicle {2}" + ", including an odometer log of {3} miles on {4:d} reported by {5}.", odometer, date, vehicleId, conflictingLog.EndOdometer, conflictingLog.Date, conflictingLog.EmployeeName)); } } public Log GetFirstConflictingOdometerChronologyLog(string vehicleId, DateTime date, int odometer) { // TODO HACK for testing, delete later try { if (_db.Logs.Count() == 0) return null; } catch (NullReferenceException) { return null; } var conflictingLogsQuery = from log in _db.Logs let existingDate = log.Date let existingOdometer = log.EndOdometer where log.VehicleId == vehicleId && !(existingDate == date || existingOdometer == odometer || existingDate < date && existingOdometer < odometer || existingDate > date && existingOdometer > odometer) orderby existingOdometer descending select log; return conflictingLogsQuery.FirstOrDefault(); } #endregion #region Employee Name public string GetEmployeeNameCorrected(string term) { var names = GetEmployeeNames().ToList(); return (from potentialName in names let potentialNameClean = Regex.Replace(potentialName, @"\s*\(.*\)", "").ToLower() let name = term.ToLower() let similarity = Algorithms.Similarity(potentialNameClean, name) where similarity > .7 || potentialNameClean.StartsWith(name) orderby similarity descending select potentialName).FirstOrDefault(); } public IEnumerable GetEmployeeNamesAutocomplete(string term) { var termLower = term.ToLower(); return from name in GetEmployeeNames() let nameLower = name.ToLower() where nameLower.StartsWith(termLower) || Algorithms.Similarity(nameLower, termLower) > .8 select name; } public IEnumerable GetEmployeeNames() { var names = _db.Vehicles.Select(v => v.Assigned).Where(n => !String.IsNullOrEmpty(n) && n != "to be sold"); var logNames = (from l in _db.Logs select l.EmployeeName); return names.Union(logNames).Distinct(); } #endregion #region City public string GetCitiesCorrected(string term) { var termLower = term.ToLower(); return (from city in GetCities().ToList() let cityToLower = city.Name.ToLower() let similarity = Algorithms.Similarity(cityToLower, termLower) where city.Name.StartsWith(term) || Algorithms.Similarity(cityToLower, termLower) > .8 select city.Name).FirstOrDefault(); } public IEnumerable GetCitiesAutocomplete(string term) { return from city in GetCities() where city.Name.StartsWith(term) select city.Name; } private IQueryable GetCities() { return _db.Cities; } #endregion public void Dispose() { _db.Dispose(); } } }