public ActionResult QuestionsWithResponses()
        {
            //pull everything that has been coded as a question by an expert
            var questionsQuery = (from comment in Db.CommentTimelines
                                 .Include(c => c.ExpertCoding)
                                 .Include(c => c.AnswerCodings)
                                 .Include(c => c.ProgrammingState)
                                 .Include(c => c.QuestionCodings)
                                 .Include(c => c.TimelineCodeDocuments)
                                 where
                                  (
                                    comment.ExpertCoding.PrimaryModifier == "Question"
                                    || comment.ExpertCoding.SecondaryModifier == "Question"
                                    || comment.ExpertCoding.TertiaryModifier == "Question"
                                    )
                                    &&
                                    (
                                    comment.ExpertCoding.Category == "Code"
                                    )
                                  
                                    orderby comment.ExpertCoding.Date
                                 select comment).ToList();

            List<CommentTimelineViewModel> viewModel = new List<CommentTimelineViewModel>();
            int[] userIds = questionsQuery.Select(q => q.AuthorId).Distinct().ToArray();
            int[] eventIds = questionsQuery.Select(q => q.EventLogId).Distinct().ToArray();
            var users = (from user in OsbideDb.Users
                         where userIds.Contains(user.Id)
                                      select user).ToDictionary(u => u.Id, u => u);
            var logs = (from log in OsbideDb.EventLogs
                        where eventIds.Contains(log.Id)
                        select log).ToDictionary(l => l.Id, l => l);
            foreach(CommentTimeline timeline in questionsQuery)
            {
                CommentTimelineViewModel current = new CommentTimelineViewModel(timeline);
                current.Author = users[timeline.AuthorId];
                current.Log = logs[timeline.EventLogId];
                current.CodeBeforeComment = timeline.TimelineCodeDocuments.Where(d => d.isBeforeComment == true).ToDictionary(d => d.DocumentName, d => d);
                current.CodeAfterComment = timeline.TimelineCodeDocuments.Where(d => d.isBeforeComment == false).ToDictionary(d => d.DocumentName, d => d);
                viewModel.Add(current);
            }

            return View(viewModel);
        }
        /// <summary>
        /// Gets comment details for a particular student
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public ActionResult Student(int id)
        {
            /*
             * to fetch:
             * For each comment:
             *      Comment
             *      Option to view entire thread
             *      Chris, Adam, Carla content coding info
             *      Adam's student EC coding info
             *      size of previous / next save
             *      time of prevous /  last save
             *      NPSM state
             * */
            List<CommentTimelineViewModel> viewModel = new List<CommentTimelineViewModel>();

            //id is nice for MVC, but not very descriptive, switch to studentId in body.
            int studentId = id;
            int[] interestingEventIds =
            {
                  1  //ask for help 
                , 2  //build event
                , 7  //feed post
                , 9  //log comment
                , 10 //save
            };

            //only social events
            int[] socialEventIds = {1, 7, 9};

            //check to see if we have cached results in the DB
            var cachedResults = Db
                .CommentTimelines
                .Include(c => c.ProgrammingState)
                .Include(c => c.QuestionCodings.Select(q => q.QuestionCoding.Post))
                .Include(c => c.AnswerCodings.Select(q => q.AnswerCoding.Answer))
                .Include(c => c.ExpertCoding)
                .Include(c => c.TimelineCodeDocuments)
                .Where(c => c.AuthorId == studentId)
                ;
            if (cachedResults.Count() > 0)
            {
                var authorQuery = from user in OsbideDb.Users
                                  where user.Id == studentId
                                  select user;
                OsbideUser student = authorQuery.FirstOrDefault();
                student = (student == null) ? new OsbideUser() : student;

                var eventLogQuery = from log in OsbideDb.EventLogs
                                    where log.SenderId == studentId
                                    && socialEventIds.Contains(log.EventTypeId)
                                    select log;
                Dictionary<int, EventLog> allEventLogs = new Dictionary<int, EventLog>();
                foreach(EventLog log in eventLogQuery)
                {
                    allEventLogs.Add(log.Id, log);
                }

                foreach(CommentTimeline timeline in cachedResults)
                {
                    CommentTimelineViewModel nextViewModel = new CommentTimelineViewModel(timeline);
                    nextViewModel.Log = allEventLogs[timeline.EventLogId];
                    nextViewModel.Author = student;
                    List<TimelineCodeDocument> beforeDocuments = timeline.TimelineCodeDocuments.Where(t => t.isBeforeComment == true).ToList();
                    List<TimelineCodeDocument> afterDocuments = timeline.TimelineCodeDocuments.Where(t => t.isBeforeComment == false).ToList();
                    nextViewModel.CodeBeforeComment = new Dictionary<string, TimelineCodeDocument>();
                    nextViewModel.CodeAfterComment = new Dictionary<string, TimelineCodeDocument>();
                    foreach(TimelineCodeDocument tcd in beforeDocuments)
                    {
                        nextViewModel.CodeBeforeComment.Add(tcd.DocumentName, tcd);
                    }
                    foreach(TimelineCodeDocument tcd in afterDocuments)
                    {
                        nextViewModel.CodeAfterComment.Add(tcd.DocumentName, tcd);
                    }
                    viewModel.Add(nextViewModel);
                }
            }
            else
            {
                //no cached results, do it the long way...

                //this query pulls most questions (ask for help excluded?) from the analytics DB
                //and should be faster than loading all questions from the OSBIDE DB
                var commentQuery = from comment in Db.Posts
                                   where comment.AuthorId == studentId
                                   select comment;

                //convert into dictionary for lookup by logsQuery
                Dictionary<int, Post> posts = new Dictionary<int, Post>();
                foreach (Post post in commentQuery)
                {
                    posts.Add(post.OsbideId, post);
                }

                //This query will pull down all content coding questions, organized by Osbide user ID
                var contentCodingQuery = from code in Db.ContentCodings
                                         where code.AuthorId == studentId
                                         select code;
                SortedList<DateTime, ContentCoding> expertCodings = new SortedList<DateTime, ContentCoding>();
                foreach (ContentCoding coding in contentCodingQuery)
                {
                    //I was getting key mismatch (probably difference in milliseconds).  My solution was to create
                    //a new date using only coarser measures
                    DateTime dateKey = new DateTime(coding.Date.Year, coding.Date.Month, coding.Date.Day, coding.Date.Hour, coding.Date.Minute, coding.Date.Second, DateTimeKind.Utc);
                    expertCodings.Add(dateKey, coding);
                }

                //This query will pull down information obtained from my crowd-sourced content coding.
                //AnswerCodings have FK reference to the original question as well as the answer.  If a Post
                //is in the AnswerCodings table, it must be an answer
                var answeredQuestionsQuery = from answer in Db.AnswerCodings
                                           .Include(c => c.Answer)
                                           .Include(c => c.Question)
                                             where answer.Answer.AuthorId == studentId || answer.Question.AuthorId == studentId
                                             select answer;

                var allPostsQuery = from question in Db.QuestionCodings
                                        .Include(c => c.Post)
                                    where question.Post.AuthorId == studentId
                                    select question;
                Dictionary<int, PostCoding> crowdCodings = new Dictionary<int, PostCoding>();
                foreach (QuestionCoding coding in allPostsQuery)
                {
                    if (crowdCodings.ContainsKey(coding.Post.OsbideId) == false)
                    {
                        crowdCodings.Add(coding.Post.OsbideId, new PostCoding());
                        crowdCodings[coding.Post.OsbideId].OsbidePostId = coding.Post.OsbideId;
                    }
                    crowdCodings[coding.Post.OsbideId].Codings.Add(coding);
                }
                foreach (AnswerCoding coding in answeredQuestionsQuery)
                {
                    if (crowdCodings.ContainsKey(coding.Question.OsbideId) == false)
                    {
                        crowdCodings.Add(coding.Question.OsbideId, new PostCoding());
                        crowdCodings[coding.Question.OsbideId].OsbidePostId = coding.Question.OsbideId;
                    }
                    crowdCodings[coding.Question.OsbideId].Responses.Add(coding);
                }

                //grab all save and build events for this user
                Dictionary<int, SaveEvent> allSaves = new Dictionary<int, SaveEvent>();
                Dictionary<int, BuildEvent> allBuilds = new Dictionary<int, BuildEvent>();
                var savesQuery = from save in OsbideDb.SaveEvents
                                 .Include(s => s.Document)
                                 join log in OsbideDb.EventLogs on save.EventLogId equals log.Id
                                 where log.SenderId == studentId
                                 select save;
                foreach (SaveEvent saveEvent in savesQuery)
                {
                    allSaves[saveEvent.EventLogId] = saveEvent;
                }
                var buildsQuery = from build in OsbideDb.BuildEvents
                                 .Include(b => b.Documents.Select(d => d.Document))
                                  join log in OsbideDb.EventLogs on build.EventLogId equals log.Id
                                  where log.SenderId == studentId
                                  select build;
                foreach (BuildEvent buildEvent in buildsQuery)
                {
                    allBuilds[buildEvent.EventLogId] = buildEvent;
                }

                //this query pulls data directly from event logs.
                var logsQuery = from log in OsbideDb.EventLogs
                                where log.SenderId == studentId && interestingEventIds.Contains(log.EventTypeId)
                                select log;
                List<EventLog> eventLogs = logsQuery.ToList();

                Stack<EventLog> saveEvents = new Stack<EventLog>();
                List<EventLog> socialEvents = new List<EventLog>();

                foreach (EventLog log in eventLogs)
                {
                    //holds the next entry into the view model
                    CommentTimelineViewModel nextViewModel = new CommentTimelineViewModel();

                    //if we have a document save event, remember for later until we get a social event
                    if (log.LogType == SaveEvent.Name || log.LogType == BuildEvent.Name)
                    {
                        saveEvents.Push(log);
                    }
                    else
                    {
                        //social event detected

                        //1: grab previous edit information
                        string solutionName = "";
                        Dictionary<string, CodeDocument> previousDocuments = new Dictionary<string, CodeDocument>();

                        //Start with saves as they will contain more up-to-date information than last build
                        while (saveEvents.Count > 0 && saveEvents.Peek().LogType != BuildEvent.Name)
                        {
                            EventLog next = saveEvents.Pop();
                            if (allSaves.ContainsKey(next.Id))
                            {
                                SaveEvent save = allSaves[next.Id];
                                if (solutionName.Length == 0)
                                {
                                    solutionName = save.SolutionName;
                                }
                                if (save.SolutionName == solutionName)
                                {
                                    if (previousDocuments.ContainsKey(save.Document.FileName) == false)
                                    {
                                        previousDocuments[save.Document.FileName] = save.Document;
                                    }
                                }
                            }
                        }

                        //at this point, saveEvents should be empty or we should be at a build event.
                        //Finish off the snapshot with documents transferred with last build
                        if (saveEvents.Count > 0)
                        {
                            EventLog top = saveEvents.Pop();
                            if (allBuilds.ContainsKey(top.Id))
                            {
                                BuildEvent build = allBuilds[top.Id];

                                if (solutionName.Length == 0)
                                {
                                    solutionName = build.SolutionName;
                                }
                                if (build.SolutionName == solutionName)
                                {
                                    foreach (BuildDocument doc in build.Documents)
                                    {
                                        if (previousDocuments.ContainsKey(doc.Document.FileName) == false)
                                        {
                                            previousDocuments[doc.Document.FileName] = doc.Document;
                                        }
                                    }
                                }
                            }
                        }


                        //store final result in view model
                        foreach(CodeDocument document in previousDocuments.Values)
                        {
                            TimelineCodeDocument tcd = new TimelineCodeDocument()
                            {
                                CodeDocumentId = document.Id,
                                CommentTimeline = nextViewModel.Timeline,
                                DocumentContent = document.Content,
                                DocumentName = document.FileName,
                                isBeforeComment = true
                            };
                            if(nextViewModel.Timeline.TimelineCodeDocuments == null)
                            {
                                nextViewModel.Timeline.TimelineCodeDocuments = new List<TimelineCodeDocument>();
                            }
                            nextViewModel.Timeline.TimelineCodeDocuments.Add(tcd);
                        }

                        //2: grab next edit information (will have to be done on 2nd pass)

                        //grab expert content coding info
                        DateTime dateKey = new DateTime(log.DateReceived.Year,
                                                        log.DateReceived.Month,
                                                        log.DateReceived.Day,
                                                        log.DateReceived.Hour,
                                                        log.DateReceived.Minute,
                                                        log.DateReceived.Second,
                                                        DateTimeKind.Utc);
                        //I was getting key mismatch (probably difference in milliseconds).  My solution was to create
                        //a new date using only coarser measures
                        if (expertCodings.ContainsKey(dateKey))
                        {
                            nextViewModel.Timeline.ExpertCoding = expertCodings[dateKey];
                            nextViewModel.Timeline.ExpertCodingId = expertCodings[dateKey].Id;
                        }

                        //grab crowd coding info
                        var crowd = crowdCodings.Where(cc => cc.Key == log.Id).Select(k => k.Value).FirstOrDefault();
                        if (crowd != null)
                        {
                            foreach (var question in crowd.Codings)
                            {
                                TimelineQuestionCoding questionCode = new TimelineQuestionCoding()
                                {
                                    CommentTimeline = nextViewModel.Timeline,
                                    QuestionCoding = question,
                                    QuestionCodingId = question.Id
                                };
                                nextViewModel.Timeline.QuestionCodings.Add(questionCode);
                            }
                            foreach (var answer in crowd.Responses)
                            {
                                TimelineAnswerCoding responseCoding = new TimelineAnswerCoding()
                                {
                                    CommentTimeline = nextViewModel.Timeline,
                                    AnswerCoding = answer,
                                    AnswerCodingId = answer.Id
                                };
                                nextViewModel.Timeline.AnswerCodings.Add(responseCoding);
                            }
                        }

                        //grab NPSM state info
                        //we want the most recent NPSM state that occurred before the comment was made
                        var npsmQuery = from npsm in Db.TimelineStates
                                        where npsm.StartTime <= log.DateReceived && npsm.IsSocialEvent == false
                                        && npsm.UserId == log.SenderId
                                        && npsm.State != "--"
                                        orderby npsm.Id ascending
                                        select npsm;
                        TimelineState state = npsmQuery.Take(1).FirstOrDefault();
                        if (state != null)
                        {
                            nextViewModel.Timeline.ProgrammingState = state;
                            nextViewModel.Timeline.ProgrammingStateId = state.Id;
                        }


                        //add in comment information
                        if (posts.ContainsKey(log.Id) == true)
                        {
                            nextViewModel.Timeline.Comment = posts[log.Id].Content;
                        }
                        else
                        {
                            //not found in pre-query.  Pull manually
                            if (log.LogType == FeedPostEvent.Name)
                            {
                                FeedPostEvent feedPost = OsbideDb.FeedPostEvents.Where(fpe => fpe.EventLogId == log.Id).FirstOrDefault();
                                if (feedPost != null)
                                {
                                    nextViewModel.Timeline.Comment = feedPost.Comment;
                                }
                            }
                            else if (log.LogType == LogCommentEvent.Name)
                            {
                                LogCommentEvent logComment = OsbideDb.LogCommentEvents.Where(fpe => fpe.EventLogId == log.Id).FirstOrDefault();
                                if (logComment != null)
                                {
                                    nextViewModel.Timeline.Comment = logComment.Content;
                                }
                            }
                            else if (log.LogType == AskForHelpEvent.Name)
                            {
                                AskForHelpEvent ask = OsbideDb.AskForHelpEvents.Where(fpe => fpe.EventLogId == log.Id).FirstOrDefault();
                                if (ask != null)
                                {
                                    nextViewModel.Timeline.Comment = ask.UserComment + "\n" + ask.Code;
                                }
                            }
                        }
                        nextViewModel.Log = log;
                        nextViewModel.Timeline.EventLogId = log.Id;
                        nextViewModel.Timeline.AuthorId = log.SenderId;
                        nextViewModel.Author = log.Sender;
                        if(nextViewModel.Timeline.ExpertCoding == null)
                        {
                            nextViewModel.Timeline.ExpertCoding = new ContentCoding();
                        }
                        if(nextViewModel.Timeline.ProgrammingState == null)
                        {
                            nextViewModel.Timeline.ProgrammingState = new TimelineState()
                            {
                                EndTime = new DateTime(2016, 01, 01),
                                StartTime = new DateTime(2016, 01, 01),
                                State = "not available"
                            };
                        }
                        viewModel.Add(nextViewModel);
                    }
                }

                //2nd pass: find code modifications made after comment.  
                for (int i = 0; i < viewModel.Count; i++)
                {
                    CommentTimelineViewModel current = viewModel[i] as CommentTimelineViewModel;
                    CommentTimelineViewModel next = new CommentTimelineViewModel();
                    if (i + 1 < viewModel.Count)
                    {
                        next = viewModel[i + 1] as CommentTimelineViewModel;
                    }
                    else
                    {
                        next.Log = eventLogs.Last();
                    }

                    List<EventLog> logsBetween = eventLogs
                        .Where(l => l.DateReceived >= current.Log.DateReceived)
                        .Where(l => l.DateReceived <= next.Log.DateReceived)
                        .ToList();

                    Dictionary<string, CodeDocument> nextDocuments = new Dictionary<string, CodeDocument>();
                    string solutionName = "";
                    foreach (EventLog log in logsBetween)
                    {
                        if (log.LogType == SaveEvent.Name)
                        {
                            if (allSaves.ContainsKey(log.Id))
                            {
                                SaveEvent save = allSaves[log.Id];
                                if (solutionName.Length == 0)
                                {
                                    solutionName = save.SolutionName;
                                }
                                if (save.SolutionName == solutionName)
                                {
                                    if (nextDocuments.ContainsKey(save.Document.FileName) == false)
                                    {
                                        nextDocuments[save.Document.FileName] = save.Document;
                                    }
                                }
                            }
                        }
                        else if (log.LogType == BuildEvent.Name)
                        {
                            if (allBuilds.ContainsKey(log.Id))
                            {
                                BuildEvent build = allBuilds[log.Id];
                                if (solutionName.Length == 0)
                                {
                                    solutionName = build.SolutionName;
                                }
                                if (build.SolutionName == solutionName)
                                {
                                    foreach (BuildDocument doc in build.Documents)
                                    {
                                        if (nextDocuments.ContainsKey(doc.Document.FileName) == false)
                                        {
                                            nextDocuments[doc.Document.FileName] = doc.Document;
                                        }
                                    }
                                }
                            }
                        }
                    }

                    //store after documents
                    foreach (CodeDocument document in nextDocuments.Values)
                    {
                        TimelineCodeDocument tcd = new TimelineCodeDocument()
                        {
                            CodeDocumentId = document.Id,
                            CommentTimeline = current.Timeline,
                            DocumentContent = document.Content,
                            DocumentName = document.FileName,
                            isBeforeComment = false
                        };
                        if(current.Timeline.TimelineCodeDocuments == null)
                        {
                            current.Timeline.TimelineCodeDocuments = new List<TimelineCodeDocument>();
                        }
                        current.Timeline.TimelineCodeDocuments.Add(tcd);
                    }
                }

                //try jamming all this crap into DB
                Db.CommentTimelines.AddRange(viewModel.Select(m => m.Timeline).ToList());
                Db.SaveChanges();
            }

            return View(viewModel);
        }