using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using Interfaces;
using SQLite.Net;
using SQLite.Net.Platform.Win32;

namespace SQLiteRepository
{
    public class SQLiteRepository : IRepository
    {
        private readonly SQLiteConnection _connection;
        private readonly ILogger _logger;
        private string _path = "flexitimedb.db";

        public SQLiteRepository(ILogger logger)
        {
            _path =
                new Uri(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().CodeBase), "flexitimedb.db"))
                    .LocalPath;
            if (logger == null) throw new ArgumentNullException(nameof(logger));
            _logger = logger;

            _connection = new SQLiteConnection(new SQLitePlatformWin32(), _path);

            _connection.CreateTable<CardUniqueId>();
            _connection.CreateTable<UserIdentity>();
            _connection.CreateTable<TimeLogDb>();
            _connection.CreateTable<GroupDb>();
            _connection.CreateTable<UserGroupJoinDb>();
            _logger.Trace("Initialised SQLite Repository");
        }

        public UserList GetUsers(int pageNumber = -1, int pageSize = -1, int groupId = -1)
        {
            var ret = new UserList();
            List<UserIdentity> users;
            int userCount;
            if (pageNumber != -1 && pageSize != -1)
            {
                users = _connection.Query<UserIdentity>(SQLiteProcedures.GET_ALL_USERS_PAGINATE,
                    pageSize, (pageNumber - 1) * pageSize);
                userCount = _connection.ExecuteScalar<int>(SQLiteProcedures.GET_TOTAL_USER_COUNT);
            }
            else if (groupId != -1)
            {
                users =
                    _connection.Query<UserIdentity>(
                        SQLiteProcedures.GET_ALL_USERS_BY_GROUP,
                        groupId);
                userCount = users.Count;
            }
            else
            {
                users = _connection.Query<UserIdentity>(SQLiteProcedures.GET_ALL_USERS);
                userCount = users.Count;
            }

            if (!users.Any())
            {
                if (pageNumber == -1 && pageSize == -1)
                {
                    ret.PageNumber = 1;
                    ret.PageSize = 20;
                }
                else
                {
                    ret.PageNumber = pageNumber;
                    ret.PageSize = pageSize;
                }
                return ret;
            }

            foreach (var user in users)
            {
                var userObj = ChangeToUserObject(user);

                userObj.AssociatedIdentifiers = GetAssociatedIdentifiers(user.Id);
                userObj.State = GetUserState(GetLogDirection(user.Id));
                userObj.LastEventDateTime = GetLastLogDateTime(GetLastTimeLog(user.Id));
                userObj.Groups = GetGroups(user.Id);
                ret.Users.Add(userObj);
            }
            if (pageNumber == -1 && pageSize == -1)
            {
                ret.PageSize = 10;
                ret.PageNumber = 1;
            }
            else
            {
                ret.PageSize = pageSize;
                ret.PageNumber = pageNumber;
            }
            ret.TotalUserCount = userCount;
            return ret;
        }
        
        public UserList Search(string searchParam)
        {
            _logger.Trace("Searching SQLite database for the term: {0}", searchParam);
            var ret = new UserList();
            searchParam = string.Format("%{0}%", searchParam);
            var users = _connection.Query<UserIdentity>(
                SQLiteProcedures.SEARCH_USER_LIST,
                searchParam, searchParam);

            _logger.Trace("Got {0} results for term: {1}", users.Count, searchParam);
            if (!users.Any())
            {
                ret.PageNumber = 1;
                ret.PageSize = 20;
                return ret;
            }

            foreach (var user in users)
            {
                var userObj = ChangeToUserObject(user);
                var cards = _connection.Query<CardUniqueId>(
                    SQLiteProcedures.GET_CARDS_BY_USER_ID,
                    user.Id);

                foreach (var card in cards)
                {
                    userObj.AssociatedIdentifiers.Add(new Identifier()
                    {
                        UniqueId = card.CardUId,
                        IsAssociatedToUser = true,
                        Id = card.Id
                    });
                }
                userObj.State = GetUserState(GetLogDirection(user.Id));
                ret.Users.Add(userObj);
            }
            //TODO: figure out paging here. - should there be any?
            ret.PageSize = 20;
            ret.PageNumber = 1;

            return ret;
        }

        public User GetUser(int id)
        {
            var ret = new User();

            try
            {
                var users = _connection.Query<UserIdentity>(
                        SQLiteProcedures.GET_USER_BY_ID,
                        id);

                if (!users.Any()) return ret;

                var user = users.First();
                ret = ChangeToUserObject(user);
                ret.Groups = GetGroups();
                var usersGroups = GetGroups(user.Id);
                foreach (var group in usersGroups)
                {
                    ret.Groups.First(x => x.Id == group.Id).IsAssociatedToUser = true;
                }
                var cards = _connection.Query<CardUniqueId>(
                    SQLiteProcedures.GET_CARDS_BY_USER_ID,
                    user.Id);

                foreach (var card in cards)
                {
                    ret.AssociatedIdentifiers.Add(new Identifier { UniqueId = card.CardUId, IsAssociatedToUser = true, Id = card.Id });
                }
            }
            catch (Exception ex)
            {
                _logger.Error(ex, "Error in GetUser with Id: {0}", id);
                ret.UserId = id;
                ret.FirstName = ret.LastName = string.Empty;
                ret.HoursPerWeek = -1.0f;
                ret.IsContractor = false;
                ret.AssociatedIdentifiers.Clear();
            }

            return ret;
        }

        public TimeLogList GetTimeLogs(int userId)
        {
            return GetTimeLogs(userId, DateTime.UtcNow);
        }

        public TimeLogList GetTimeLogs(int userId, DateTime selectedDate)
        {
            var ret = new TimeLogList { SelectedDate = selectedDate.Date };
            var calendarWeek = GetIso8601CalendarWeek(selectedDate);
            ret.CalendarWeek = calendarWeek;

            try
            {
                ret.TimeLogs = GetTimeLogList(userId, calendarWeek, selectedDate.Year);
            }
            catch (Exception ex)
            {
                _logger.Error(ex, "Error in GetTimeLogs with Id: {0} and selected date: {1}, Exception: {2}", userId, selectedDate, ex);
            }

            try
            {
                ret.HoursPerWeekMinutes = GetUserContractedHours(userId) * 60.0f;
            }
            catch (Exception ex)
            {
                _logger.Error(ex, "Error in GetUserContracterHours with Id: {0} and selected date: {1}, Exception: {2}", userId, selectedDate, ex);
            }

            ret.UserInformation = GetUser(userId);
            return ret;
        }

        private float GetUserContractedHours(int userId)
        {
            var hoursQuery = _connection.Query<UserIdentity>(SQLiteProcedures.GET_USER_CONTRACTED_HOURS, userId);
            if (hoursQuery.Any())
            {
                return hoursQuery.First().HoursPerWeek;
            }
            return -1.0f;
        }

        public IdentifierList GetUnassignedIdentifierList()
        {
            var ret = new IdentifierList();
            var cardQuery = _connection.Query<CardUniqueId>(
                SQLiteProcedures.GET_UNASSIGNED_CARD_LIST,
                Constants.UNASSIGNED_CARD_USER_ID);

            foreach (var card in cardQuery)
            {
                ret.data.Add(new Identifier
                {
                    Id = card.Id,
                    IsAssociatedToUser = card.UserId_FK != Constants.UNASSIGNED_CARD_USER_ID,
                    UniqueId = card.CardUId,
                    LastUsed = card.LastUsed.DateTime
                });
            }
            return ret;
        }

        //TODO: Check time logs table on update to ensure associated cards/unique identifiers are removed/added as appropriate.
        public OperationResponse UpdateUser(User user, out int userIdResult)
        {
            //if(user.UserId <=0) return OperationResponse.FAILED;

            var ret = OperationResponse.NONE;
            var cardIds = new List<int>();

            //Get a list of current associated identifiers, convert into a list of Identifier Objects..
            var currentCards =
                _connection.Query<CardUniqueId>(SQLiteProcedures.GET_CARDS_BY_USER_ID, user.UserId)
                    .Select(x => new Identifier { Id = x.Id, IsAssociatedToUser = true, UniqueId = x.CardUId });
            var cardsToRemove = currentCards.Except(user.AssociatedIdentifiers).Select(x => x.Id).ToList();

            #region GetUnique Identifiers
            foreach (var card in user.AssociatedIdentifiers)
            {
                var existingCard = _connection.Query<CardUniqueId>(
                    SQLiteProcedures.GET_CARDS_BY_UNIQUE_ID, card.UniqueId);

                if (!existingCard.Any())
                {
                    var cardInsert = new CardUniqueId { CardUId = card.UniqueId, UserId_FK = -1 };
                    _connection.Insert(cardInsert);
                    cardIds.Add(cardInsert.Id);
                    if (ret < OperationResponse.CREATED)
                        ret = OperationResponse.CREATED;    //only change it if my status supercedes.
                }
                else
                {
                    cardIds.Add(existingCard.First().Id);
                    if (ret < OperationResponse.UPDATED)
                        ret = OperationResponse.UPDATED;
                }
            }
            #endregion

            #region Update/Create User
            int userId;

            if (user.UserId != -1)
            {
                //edit..
                _connection.Query<UserIdentity>(
                    SQLiteProcedures.UPDATE_USER_DETAILS,
                    user.FirstName,
                    user.LastName,
                    user.HoursPerWeek,
                    user.IsContractor,
                    user.UserId
                );
                userId = user.UserId;
            }
            else
            {
                var userInsert = new UserIdentity
                {
                    FirstName = user.FirstName,
                    LastName = user.LastName,
                    HoursPerWeek = user.HoursPerWeek,
                    IsContractor = user.IsContractor
                };
                _connection.Insert(userInsert);
                userId = userInsert.Id;
                if (ret < OperationResponse.CREATED)
                    ret = OperationResponse.CREATED;
            }
            #endregion

            #region Update Card/Unique Id entries.
            foreach (var cardId in cardIds)
            {
                _connection.Query<CardUniqueId>(
                    SQLiteProcedures.UPDATE_CARD_USER_ID,
                    userId, cardId);
            }
            foreach (var card in cardsToRemove)
            {
                _connection.Query<CardUniqueId>(
                    SQLiteProcedures.UPDATE_CARD_USER_ID,
                    -1, card);
            }
            #endregion

            #region Update Group Associations

            SetUserGroups(userId, user.Groups.Where(x => x.IsAssociatedToUser).ToList());

            #endregion

            userIdResult = userId;
            return ret;
        }

        public LogEventResponse LogEventTime(Identifier identifier, out int logId)
        {
            var ret = new LogEventResponse();
            var cardIdQuery = _connection.Query<CardUniqueId>(
                   SQLiteProcedures.GET_CARDS_BY_UNIQUE_ID,
                   identifier.UniqueId);

            #region Get/Insert the PK Id Identifier to associate the time log to.
            var ident = new CardUniqueId();
            if (!cardIdQuery.Any())
            {
                //new card, create it!
                ident.UserId_FK = -1;
                ident.CardUId = identifier.UniqueId;
                _connection.Insert(ident);
                UpdateIdentifierLastUsed(DateTimeOffset.UtcNow, ident.Id);
                logId = -1;
                //dont try to log any timelogs against this card, as it is unassigned to a user.
                ret.ProcessResponse=OperationResponse.SUCCESS;
                ret.Direction = LogDirection.UNKNOWN;
                return ret;
            }
            else
            {
                //TODO: log when more than one comes back. should NEVER happen but....
                ident = cardIdQuery.First();
            }
            #endregion

            // Get The User Direction (are they going in or out)?
            var logDirection = GetLogDirection(ident.UserId_FK);

            #region Check the user hasnt registered an event in the last few minutes..

            if (ident.UserId_FK != -1)
            {   //only check log gap if the card is associated to a user
                var hysteresisThresholdMinutes = Convert.ToInt32(ConfigurationHandler.ConfigurationHandler.GetConfiguration("SwipeTimeGap") ?? "3");
                var threshold = DateTime.UtcNow.AddMinutes(0 - hysteresisThresholdMinutes);
                var logs = _connection.Query<TimeLogDb>(
                    SQLiteProcedures.GET_LOGS_IN_LAST_X_MINUTES,
                    threshold.Ticks, ident.UserId_FK);
                _logger.Trace("Checking last swipe event gap");
                if (logs.Any())
                {
                    _logger.Error("Not logging event for user id: {0}, logged event within TimeGap Threshold of {1}", ident.UserId_FK, threshold);
                    logId = -1;
                    ret.ProcessResponse = OperationResponse.FAILED;
                    ret.Direction = LogDirection.UNKNOWN;
                    return ret;
                }
            }

            #endregion

            #region Get the current time (for swiping). and calendar week/year to help recall the data.
            var logTime = DateTime.UtcNow;
            var calendarWeek = GetIso8601CalendarWeek(logTime);
            var year = logTime.Year;
            #endregion

            var timeLog = new TimeLogDb
            {
                SwipeEventDateTime = DateTime.UtcNow,
                UserId_FK = ident.UserId_FK,
                IdentifierId = ident.Id,
                Direction = logDirection,
                Year = year,
                CalendarWeek = calendarWeek,
                Source = LogSourceDb.IDENTIFIER
            };

            _connection.Insert(timeLog);
            UpdateIdentifierLastUsed(timeLog.SwipeEventDateTime, timeLog.IdentifierId);

            logId = timeLog.Id;
            ret.Direction = (LogDirection)(int)logDirection;
            ret.ProcessResponse = OperationResponse.SUCCESS;
            return ret;
        }

        /*Groups*/
        //TODO: check group name can only be entered once.
        public OperationResponse CreateGroup(Group group, out int groupId)
        {
            var groupDb = new GroupDb { GroupName = group.Name };
            var resp = _connection.Insert(groupDb);
            groupId = groupDb.GroupId;
            return OperationResponse.CREATED;
        }

        public List<Group> GetGroups(int userId = -1)
        {
            var ret = new List<Group>();
            List<GroupDb> query;
            if (userId == -1)
            {
                query = _connection.Query<GroupDb>("select gp.GroupId, gp.GroupName, " +
                                                   "sum(case when gp.GroupId = ujdb.GroupId_FK then 1 else 0 end) as AssignedUserCount " +
                                                   "from GroupDb gp " +
                                                   "left join UserGroupJoinDb ujdb " +
                                                   "on ujdb.GroupId_FK = gp.GroupId " +
                                                   "group by gp.GroupId");
            }
            else
            {
                query =
                    _connection.Query<GroupDb>(
                        "select gdb.GroupId, gdb.GroupName, gdb.AssignedUserCount" +
                        " from GroupDb gdb" +
                        " left join UserGroupJoinDb ujdb" +
                        " on gdb.GroupId = ujdb.GroupId_FK" +
                        " where ujdb.UserId_FK = ?",
                        userId);
            }
            foreach (var group in query)
            {
                ret.Add(new Group
                {
                    Id = group.GroupId,
                    Name = group.GroupName,
                    UserCount = int.Parse(group.AssignedUserCount ?? "0")
                });
            }
            return ret;
        }

        public Group GetGroup(int groupId)
        {
            var query = _connection.Query<GroupDb>("select * from GroupDb where GroupId = ?", groupId);
            if (query.Any())
            {
                var group = query.First();
                return new Group
                {
                    Id = group.GroupId,
                    Name = group.GroupName,
                    UserCount = 0
                };
            }
            return new Group();
        }

        public OperationResponse UpdateGroup(Group group)
        {
            //TODO: I would probably prefer to do this manually.... 
            var resp = _connection.Query<GroupDb>("update GroupDb set GroupName=? where GroupId=?", group.Name, group.Id);
            //var resp = _connection.Update(groupDb);
            return OperationResponse.UPDATED;
        }

        public OperationResponse DeleteGroup(int groupId)
        {
            _connection.Delete<GroupDb>(groupId);
            return OperationResponse.DELETED;
        }

        public OperationResponse DeleteLog(TimeLog log)
        {
            var query = _connection.Query<TimeLogDb>(
                "select * from TimeLogDb where Id=?", log.Id);

            if (!query.Any())
                return OperationResponse.FAILED;

            _connection.ExecuteScalar<TimeLogDb>("delete from TimeLogDb where Id=?", log.Id);

            return OperationResponse.DELETED;
        }

        public OperationResponse CreateLog(TimeLog log)
        {
            var calendarWeek = GetIso8601CalendarWeek(log.EventTime.UtcDateTime);
            var year = log.EventTime.Year;
            var dbLog = new TimeLogDb
            {
                SwipeEventDateTime = log.EventTime,
                Direction = (LogDirectionDb)(int)log.Direction,
                Year = year,
                CalendarWeek = calendarWeek,
                Source = (LogSourceDb)(int)log.Source,
                UserId_FK = log.UserId,
                IdentifierId = ConvertSourceToIdentifierId(log.Source),
            };
            _connection.Insert(dbLog);
            return OperationResponse.CREATED;
        }
        
        public OperationResponse UpdateLog(TimeLog log)
        {
            var query = _connection.Query<TimeLogDb>(
                "select * from TimeLogDb where Id=?", log.Id);
            if(!query.Any())
                return OperationResponse.FAILED;

            if (log.CalendarWeek > 52 || log.CalendarWeek < 1)
            {
                log.CalendarWeek = GetIso8601CalendarWeek(log.EventTime.UtcDateTime);
            }
            if (log.Year < 2017)
            {
                log.Year = log.EventTime.Year;
            }
            _connection.ExecuteScalar<TimeLogDb>(
                "update TimeLogDb set UserId_FK=?, Direction=?,SwipeEventDateTime=?,CalendarWeek=?,Year=?,Source=? where Id=?",
                log.UserId, (LogDirectionDb) (int) log.Direction, log.EventTime, log.CalendarWeek, log.Year,
                (LogSourceDb) (int) log.Source, log.Id);

            return OperationResponse.UPDATED;
        }

        private DateTime GetLastLogDateTime(TimeLogDb timeLog)
        {
            if (timeLog != null)
            {
                return timeLog.SwipeEventDateTime.DateTime;
            }
            return DateTime.MinValue;
        }

        private bool GetUserState(LogDirectionDb logDirection)
        {
            switch (logDirection)
            {
                case LogDirectionDb.OUT:
                    return true;
                default:
                    return false;
            }
        }

        private int ConvertSourceToIdentifierId(LogSource logSource)
        {
            switch (logSource)
            {
                case LogSource.UI:
                    return -100;
                case LogSource.TRAYAPP:
                    return -200;
                default:
                    return -10;
            }
        }

        private bool SetUserGroups(int userId, List<Group> groups)
        {
            var groupIds = GetGroupIds(groups.Select(x => x.Name).ToList());
            return SetUserGroups(userId, groupIds);
        }

        private bool SetUserGroups(int userId, int[] groupIds)
        {
            //remove the existing user>group associations
            _connection.Query<UserGroupJoinDb>("delete from UserGroupJoinDb where UserId_FK = ?", userId);
            //add the new group associations.
            _connection.InsertAll(groupIds.Select(x => new UserGroupJoinDb { GroupId_FK = x, UserId_FK = userId }));
            return true;
        }

        private int[] GetGroupIds(List<string> groupNames)
        {
            var ret = new List<int>();

            foreach (var g in groupNames)
            {
                var query = _connection.Query<GroupDb>("select GroupId from GroupDb where GroupName=?", g);
                if (!query.Any()) continue;
                var id = query.First();
                ret.Add(id.GroupId);
            }
            return ret.ToArray();
        }

        private List<DailyLogs> GetTimeLogList(int userId, int calendarWeek, int year)
        {
            var timeLogList = _connection.Query<TimeLogDb>(
                SQLiteProcedures.GET_TIMELOGS,
                userId, calendarWeek, year);

            var timeLogs = timeLogList.Select(x => new TimeLog
            {
                Id = x.Id,
                CalendarWeek = x.CalendarWeek,
                Direction = (LogDirection)x.Direction,
                IdentifierId = x.IdentifierId,
                EventTime = x.SwipeEventDateTime,
                UserId = x.UserId_FK,
                Year = x.Year
            }).OrderBy(x=>x.EventTime.UtcDateTime).ToList();

            var dict = new Dictionary<DayOfWeek, DailyLogs>();
            var logList = new List<DailyLogs>();

            //make sure each day of the week is accounted for in the dictionary.
            foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek)))
            {
                dict.Add(day, new DailyLogs());
            }

            //add the logs to the respective day of the week.
            foreach (var log in timeLogs.OrderBy(x=>x.EventTime))
            {
                dict[log.EventTime.DayOfWeek].Logs.Add(log);
            }
            var logGroups = timeLogs.GroupBy(x => x.EventTime.DayOfWeek);
            foreach (var group in logGroups)
            {
                var groupLogs = group.ToList();
                var dailyLog = new DailyLogs
                {
                    Logs = groupLogs,
                    Day = group.Key,
                    DayOfWeek = group.Key.ToString()
                };
                dailyLog.DailyTotal = CalculateDailyTotal(dailyLog);
                logList.Add(dailyLog);
            }
            foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek)))
            {
                if (logList.Any(x => x.Day == day)) continue;
                var dailyLog = new DailyLogs { Day = day, DayOfWeek = day.ToString() };
                logList.Add(dailyLog);
            }

            foreach (var dailyCollection in dict)
            {
                dailyCollection.Value.DailyTotal = CalculateDailyTotal(dailyCollection.Value);
            }

            return logList.OrderBy(x => ((int)x.Day + 6) % 7).ToList();
        }

        private double CalculateDailyTotal(DailyLogs dailyLogs)
        {
            var totalInTime = TimeSpan.FromSeconds(0);
            var logs = dailyLogs.Logs.OrderBy(x => x.EventTime.UtcDateTime).ToArray();
            var totalCalcMax = IsOdd(logs.Length) ? logs.Length - 1 : logs.Length;
            for (int i = 0; i < totalCalcMax; i += 2)
            {
                totalInTime += (logs[i + 1].EventTime - logs[i].EventTime);
            }
            return Math.Round(totalInTime.TotalMinutes, 2);
        }

        /// <summary>
        /// determines if the number is an odd or even value
        /// </summary>
        /// <param name="value">number to determine is odd or even</param>
        /// <returns>true - number is odd</returns>
        private bool IsOdd(int value)
        {
            return value % 2 != 0;
        }

        /// <summary>
        /// Get the new direction for the user based on previous entry logs in the system.
        /// </summary>
        /// <remarks>
        /// If the user has not logged in today, the direction will be In.
        /// If the user has logged in already today, the direction will be the opposite of the last
        /// recorded log direction. ("out" if "in", "in" if "out")
        /// </remarks>
        /// <param name="userId">Id of the user to get the log direction of.</param>
        /// <returns><see cref="LogDirectionDb"/> indicating what direction the new log is.</returns>
        private LogDirectionDb GetLogDirection(int userId)
        {
            var logDirection = LogDirectionDb.UNKNOWN;
            if (userId != -1)
            {
                var lastEntry = GetLastTimeLog(userId);
                if (lastEntry != null)
                {
                    // See if the datetime retrieved is yesterday. If yesterday, logDirection = true (in)
                    if (IsLogDateTimeYesterdayOrOlder(lastEntry.SwipeEventDateTime.DateTime))
                    {
                        logDirection = LogDirectionDb.IN;
                    }
                    else
                    {
                        // we have a time log from today already, so just do the opposite of what we last did!
                        if (lastEntry.Direction == LogDirectionDb.IN)
                            logDirection = LogDirectionDb.OUT;
                        else if (lastEntry.Direction == LogDirectionDb.OUT)
                            logDirection = LogDirectionDb.IN;
                    }
                }
                else
                {
                    //assume its the first then!
                    logDirection = LogDirectionDb.IN;
                }
            }
            return logDirection;
        }

        private TimeLogDb GetLastTimeLog(int userId)
        {
            var lastEntry = _connection.Query<TimeLogDb>(
                SQLiteProcedures.GET_LAST_TIMELOG_DIRECTION,
                userId);
            if (lastEntry.Any())
            {
                return lastEntry.First();
            }
            return null;
        }

        private void UpdateIdentifierLastUsed(DateTimeOffset dt, int cardId)
        {
            var res = _connection.ExecuteScalar<CardUniqueId>(SQLiteProcedures.UPDATE_CARD_LAST_USED,
                dt,
                cardId);
        }

        private List<Identifier> GetAssociatedIdentifiers(int userId)
        {
            var cards = _connection.Query<CardUniqueId>(
                    SQLiteProcedures.GET_CARDS_BY_USER_ID,
                    userId);
            var ret = new List<Identifier>();
            foreach (var card in cards)
            {
                ret.Add(new Identifier()
                {
                    UniqueId = card.CardUId,
                    IsAssociatedToUser = true,
                    Id = card.Id
                });
            }
            return ret;
        }

        /// <summary>
        /// Get the calendar week of the year according to the ISO8601 standard (starts monday).
        /// </summary>
        /// <param name="date">the date to get the calendar week of.</param>
        /// <returns>the calendar week of the year in integer form (1-52)</returns>
        private int GetIso8601CalendarWeek(DateTime date)
        {
            var day = CultureInfo.InvariantCulture.Calendar.GetDayOfWeek(date);
            if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday)
            {
                date = date.AddDays(3);
            }
            return CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(date, CalendarWeekRule.FirstFourDayWeek,
                DayOfWeek.Monday);
        }

        /// <summary>
        /// Check whether the specified DateTime is from yesterday or older.
        /// </summary>
        /// <param name="dt">the DateTime object to check is yesterday or older</param>
        /// <returns>true - is yesterday or older.</returns>
        private bool IsLogDateTimeYesterdayOrOlder(DateTime dt)
        {
            return dt.Date.CompareTo(DateTime.Today.Date) < 0;
        }

        private User ChangeToUserObject(UserIdentity user)
        {
            return new User
            {
                UserId = user.Id,
                FirstName = user.FirstName,
                LastName = user.LastName,
                HoursPerWeek = user.HoursPerWeek,
                IsContractor = user.IsContractor
            };
        }
    }
}
