private void IntegrationOnBoardChanged(object sender, BoardChangedEventArgs boardChangedEventArgs)
		{
			var sb = new StringBuilder();
			if (boardChangedEventArgs.BoardStructureChanged)
				sb.AppendLine("Board structure changed.");

			if (boardChangedEventArgs.AddedCards.Any())
				sb.AppendLine(string.Format("{0} card(s) were added.", boardChangedEventArgs.AddedCards.Count));

			if (boardChangedEventArgs.UpdatedCards.Any())
				sb.AppendLine(string.Format("{0} card(s) were updated.", boardChangedEventArgs.UpdatedCards.Count));

			if (boardChangedEventArgs.MovedCards.Any())
				sb.AppendLine(string.Format("{0} card(s) were moved.", boardChangedEventArgs.MovedCards.Count));

			if (boardChangedEventArgs.BlockedCards.Any())
				sb.AppendLine(string.Format("{0} card(s) were blocked.", boardChangedEventArgs.BlockedCards.Count));

			ShowMessage(sb.ToString());
		}
		private CheckForUpdatesLoopResult SetupCheckForUpdatesLoop()
		{
			const int pulse = 1000;
			var pollingInterval = (long) _integrationSettings.CheckForUpdatesIntervalSeconds*1000;

			var stopWatch = new System.Diagnostics.Stopwatch();
			stopWatch.Start();

			do
			{
				if (!stopWatch.IsRunning) stopWatch.Restart();
				if (ShouldContinue)
				{
					while (stopWatch.ElapsedMilliseconds < pollingInterval)
					{
						if (!ShouldContinue) return CheckForUpdatesLoopResult.Exit;
						Thread.Sleep(pulse);
					}
				}
				try
				{
					stopWatch.Stop();

					//Now do the work
					var checkResults = _api.CheckForUpdates(_board.Id, _board.Version);

					if (checkResults == null) continue;

					OnBoardStatusChecked(new BoardStatusCheckedEventArgs {HasChanges = checkResults.HasUpdates});

					if (!checkResults.HasUpdates) continue;

					try
					{
						_boardLock.EnterUpgradeableReadLock();

						var boardChangedEventArgs = new BoardChangedEventArgs();

						if (checkResults.Events.Any(x => x.RequiresBoardRefresh))
						{
							boardChangedEventArgs.BoardWasReloaded = true;
							OnBoardChanged(boardChangedEventArgs);
							return CheckForUpdatesLoopResult.Continue;
						}

						//Now we need to spin through and update the board
						//and create the information to event
						foreach (var boardEvent in checkResults.Events)
						{
							try
							{
								switch (GetEventType(boardEvent.EventType))
								{
									case EventType.CardCreation:
										var addCardEvent = CreateCardAddEvent(boardEvent, checkResults.AffectedLanes);
										if (addCardEvent != null) boardChangedEventArgs.AddedCards.Add(addCardEvent);
										break;
									case EventType.CardMove:
										var movedCardEvent = CreateCardMoveEvent(boardEvent, checkResults.AffectedLanes);
										if (movedCardEvent != null) boardChangedEventArgs.MovedCards.Add(movedCardEvent);
										break;
									case EventType.CardFieldsChanged:
										var changedFieldsEvent = CreateCardUpdateEvent(boardEvent, checkResults.AffectedLanes);
										if (changedFieldsEvent != null) boardChangedEventArgs.UpdatedCards.Add(changedFieldsEvent);
										break;
									case EventType.CardDeleted:
										boardChangedEventArgs.DeletedCards.Add(CreateCardDeletedEvent(boardEvent));
										break;
									case EventType.CardBlocked:
										if (boardEvent.IsBlocked)
											boardChangedEventArgs.BlockedCards.Add(CreateCardBlockedEvent(boardEvent,
												checkResults.AffectedLanes));
										else
											boardChangedEventArgs.UnBlockedCards.Add(CreateCardUnBlockedEvent(boardEvent,
												checkResults.AffectedLanes));
										break;
									case EventType.UserAssignment:
										if (boardEvent.IsUnassigning)
											boardChangedEventArgs.UnAssignedUsers.Add(CreateCardUserUnAssignmentEvent(boardEvent,
												checkResults.AffectedLanes));
										else
											boardChangedEventArgs.AssignedUsers.Add(CreateCardUserAssignmentEvent(boardEvent,
												checkResults.AffectedLanes));
										break;
									case EventType.CommentPost:
										boardChangedEventArgs.PostedComments.Add(CreateCommentPostedEvent(boardEvent));
										break;
									case EventType.WipOverride:
										boardChangedEventArgs.WipOverrides.Add(CreateWipOverrideEvent(boardEvent,
											checkResults.AffectedLanes));
										break;
									case EventType.UserWipOverride:
										boardChangedEventArgs.UserWipOverrides.Add(CreateUserWipOverrideEvent(boardEvent));
										break;
									case EventType.AttachmentChange:
										var attachmentEvent = CreateAttachmentEvent(boardEvent);
										if (attachmentEvent != null) boardChangedEventArgs.AttachmentChangedEvents.Add(attachmentEvent);
										break;

									case EventType.CardMoveToBoard:
										boardChangedEventArgs.CardMoveToBoardEvents.Add(CreateCardMoveToBoardEvent(boardEvent));
										break;

									case EventType.CardMoveFromBoard:
										boardChangedEventArgs.CardMoveFromBoardEvents.Add(CreateCardMoveFromBoardEvent(boardEvent));
										break;

									case EventType.BoardEdit:
										boardChangedEventArgs.BoardEditedEvents.Add(CreateBoardEditedEvent(boardEvent));
										boardChangedEventArgs.BoardStructureChanged = true;
										break;

									case EventType.BoardCardTypesChanged:
										boardChangedEventArgs.BoardCardTypesChangedEvents.Add(new BoardCardTypesChangedEvent(boardEvent.EventDateTime));
										boardChangedEventArgs.BoardStructureChanged = true;
										break;

									case EventType.BoardClassOfServiceChanged:
										boardChangedEventArgs.BoardClassOfServiceChangedEvents.Add(
											new BoardClassOfServiceChangedEvent(boardEvent.EventDateTime));
										boardChangedEventArgs.BoardStructureChanged = true;
										break;

									case EventType.Unrecognized:
										//Console.Beep();
										break;
								}
							}
							catch (Exception ex)
							{
								OnClientError(new ClientErrorEventArgs
								{
									Exception = ex,
									Message = "Error processing board change event. " + ex.Message
								});
							}
						}

						OnBoardChanged(boardChangedEventArgs);

						_boardLock.EnterWriteLock();
						try
						{
							//we need to check to see if there is a need to refresh the entire board
							//if so, we need to refresh the entire board and raise the board refreshed event
							if (!checkResults.RequiresRefesh())
							{
								//since the board does not require a refresh, then just change the effected lanes
								ApplyBoardChanges(checkResults.CurrentBoardVersion, checkResults.AffectedLanes);
							}
							else
							{
								_board = checkResults.NewPayload;
								OnBoardRefresh(new BoardInfoRefreshedEventArgs {FromBoardChange = true});
							}
						}
						catch (Exception ex)
						{
							OnClientError(new ClientErrorEventArgs
							{
								Exception = ex,
								Message = "Error applying board changes or raising board refresh."
							});
						}
						finally
						{
							_boardLock.ExitWriteLock();
						}
					}
					catch (Exception ex)
					{
						OnClientError(new ClientErrorEventArgs {Exception = ex, Message = "Error processing board events."});
					}
					finally
					{
						_boardLock.ExitUpgradeableReadLock();
					}
				}
				catch (Exception ex)
				{
					OnClientError(new ClientErrorEventArgs {Exception = ex, Message = "Error checking for board events."});
				}
			} while (ShouldContinue);

			stopWatch.Stop();

			return CheckForUpdatesLoopResult.Exit;
		}
		public virtual void OnBoardChanged(BoardChangedEventArgs eventArgs)
		{
			var eventToRaise = BoardChanged;
			if (eventToRaise != null)
				eventToRaise(this, eventArgs);
		}
		protected virtual void BoardUpdate(long boardId, BoardChangedEventArgs eventArgs, ILeanKitApi integration)
		{
		    if (eventArgs.BoardStructureChanged)
		    {
		        Log.Debug(String.Format("Received BoardStructureChanged event for [{0}], reloading Configuration", boardId));
		        // TODO: Ideally this would be ReloadConfiguration(boardId);
		        ReloadConfiguration();
		    }

		    var boardConfig = Configuration.Mappings.FirstOrDefault(x => x.Identity.LeanKit == boardId);
		    if (boardConfig == null)
		    {
		        Log.Debug(String.Format("Expected a configuration for board [{0}].", boardId));
		        return;
		    }

		    Log.Debug(String.Format("Received board changed event for board [{0}]", boardId));

            // check for content change events
		    if (!boardConfig.UpdateTargetItems)
		    {
		        Log.Info("Skipped target item update because 'UpdateTargetItems' is disabled.");
		    }
		    else
		    {
		        Log.Info("Checking for updated cards.");
			    if (eventArgs.UpdatedCards.Any())
			    {
				    var itemsUpdated = new List<string>();
				    foreach (var updatedCardEvent in eventArgs.UpdatedCards)
				    {
					    try
					    {
						    if (updatedCardEvent.UpdatedCard == null) throw new Exception("Updated card is null");
						    if (updatedCardEvent.OriginalCard == null) throw new Exception("Original card is null");

						    var card = updatedCardEvent.UpdatedCard;

						    if (string.IsNullOrEmpty(card.ExternalCardID) && !string.IsNullOrEmpty(card.ExternalSystemUrl))
						    {
							    // try to grab id from url
							    var pos = card.ExternalSystemUrl.LastIndexOf('=');
							    if (pos > 0)
								    card.ExternalCardID = card.ExternalSystemUrl.Substring(pos + 1);
						    }

						    if (string.IsNullOrEmpty(card.ExternalCardID)) continue; // still invalid; skip this card

						    if (card.Title != updatedCardEvent.OriginalCard.Title)
							    itemsUpdated.Add("Title");
						    if (card.Description != updatedCardEvent.OriginalCard.Description)
							    itemsUpdated.Add("Description");
						    if (card.Tags != updatedCardEvent.OriginalCard.Tags)
							    itemsUpdated.Add("Tags");
						    if (card.Priority != updatedCardEvent.OriginalCard.Priority)
							    itemsUpdated.Add("Priority");
						    if (card.DueDate != updatedCardEvent.OriginalCard.DueDate)
							    itemsUpdated.Add("DueDate");
						    if (card.Size != updatedCardEvent.OriginalCard.Size)
							    itemsUpdated.Add("Size");
						    if (card.IsBlocked != updatedCardEvent.OriginalCard.IsBlocked)
							    itemsUpdated.Add("Blocked");

						    if (itemsUpdated.Count <= 0) continue;

						    CardUpdated(card, itemsUpdated, boardConfig);
					    }
					    catch (Exception e)
					    {
							var card = updatedCardEvent.UpdatedCard ?? updatedCardEvent.OriginalCard ?? new Card();
							string.Format("Error processing blocked card, [{0}]: {1}", card.Id, e.Message).Error(e);
					    }
				    }
			    }
			    if (eventArgs.BlockedCards.Any())
				{
		            var itemsUpdated = new List<string>();
					foreach (var cardBlockedEvent in eventArgs.BlockedCards)
					{
						try
						{
							var card = cardBlockedEvent.BlockedCard;
							if (string.IsNullOrEmpty(card.ExternalCardID) && !string.IsNullOrEmpty(card.ExternalSystemUrl))
							{
								// try to grab id from url
								var pos = card.ExternalSystemUrl.LastIndexOf('=');
								if (pos > 0)
									card.ExternalCardID = card.ExternalSystemUrl.Substring(pos + 1);
							}

							if (string.IsNullOrEmpty(card.ExternalCardID)) continue; // still invalid; skip this card

							if (card.IsBlocked != cardBlockedEvent.BlockedCard.IsBlocked)
								itemsUpdated.Add("Blocked");

							if (itemsUpdated.Count <= 0) continue;
							CardUpdated(card, itemsUpdated, boardConfig);
						}
						catch (Exception e)
						{
							var card = cardBlockedEvent.BlockedCard ?? new Card();
							string.Format("Error processing blocked card, [{0}]: {1}", card.Id, e.Message).Error(e);
						}
					}
				}
				if (eventArgs.UnBlockedCards.Any())
				{
					var itemsUpdated = new List<string>();
					foreach (var cardUnblockedEvent in eventArgs.UnBlockedCards) 
					{
						try
						{
							var card = cardUnblockedEvent.UnBlockedCard;
							if (string.IsNullOrEmpty(card.ExternalCardID) && !string.IsNullOrEmpty(card.ExternalSystemUrl))
							{
								// try to grab id from url
								var pos = card.ExternalSystemUrl.LastIndexOf('=');
								if (pos > 0)
									card.ExternalCardID = card.ExternalSystemUrl.Substring(pos + 1);
							}

							if (string.IsNullOrEmpty(card.ExternalCardID)) continue; // still invalid; skip this card

							if (card.IsBlocked != cardUnblockedEvent.UnBlockedCard.IsBlocked)
								itemsUpdated.Add("Blocked");

							if (itemsUpdated.Count <= 0) continue;
							CardUpdated(card, itemsUpdated, boardConfig);
						}
						catch (Exception e)
						{
							var card = cardUnblockedEvent.UnBlockedCard ?? new Card();
							string.Format("Error processing unblocked card, [{0}]: {1}", card.Id, e.Message).Error(e);
						}
					}					
				}
		    }


            // check for content change events
			if (!boardConfig.CreateTargetItems)
			{
				Log.Info("Skipped checking for newly added cards because 'CreateTargetItems' is disabled.");
			}
			else
			{
				Log.Info("Checking for added cards.");
				if (eventArgs.AddedCards.Any())
				{
					foreach (var newCard in eventArgs.AddedCards.Select(cardAddEvent => cardAddEvent.AddedCard)
						.Where(newCard => newCard != null && string.IsNullOrEmpty(newCard.ExternalCardID)))
					{
						try
						{
							CreateNewItem(newCard, boardConfig);
						}
						catch (Exception e)
						{
							string.Format("Error processing newly created card, [{0}]: {1}", newCard.Id, e.Message).Error(e);
						}
					}
				}
			}

			if (!boardConfig.UpdateTargetItems && !boardConfig.CreateTargetItems)
			{
				Log.Info("Skipped checking moved cards because 'UpdateTargetItems' and 'CreateTargetItems' are disabled.");
				UpdateBoardVersion(boardId);
				return;
			}

			if (eventArgs.MovedCards.Any())
			{
				Log.Debug("Checking for cards moved to mapped lanes.");
				foreach (var movedCardEvent in eventArgs.MovedCards.Where(x => x != null && x.ToLane != null && x.MovedCard != null))
				{
					try
					{
						if (!movedCardEvent.ToLane.Id.HasValue) continue;

						if (boardConfig.LaneToStatesMap.Any() &&
							boardConfig.LaneToStatesMap.ContainsKey(movedCardEvent.ToLane.Id.Value))
						{
							var states = boardConfig.LaneToStatesMap[movedCardEvent.ToLane.Id.Value];
							if (states != null && states.Count > 0)
							{
								try
								{
									if (!string.IsNullOrEmpty(movedCardEvent.MovedCard.ExternalCardID) && boardConfig.UpdateTargetItems)
									{
										UpdateStateOfExternalItem(movedCardEvent.MovedCard, states, boardConfig);
									}
									else if (string.IsNullOrEmpty(movedCardEvent.MovedCard.ExternalCardID) && boardConfig.CreateTargetItems)
									{
										// This may be a task card being moved to the parent board, or card being moved from another board
										CreateNewItem(movedCardEvent.MovedCard, boardConfig);
									}
								}
								catch (Exception e)
								{
									Log.Error("Exception for UpdateStateOfExternalItem: " + e.Message);
								}
							}
							else
								Log.Debug(string.Format("No states are mapped to the Lane [{0}]", movedCardEvent.ToLane.Id.Value));
						}
						else
						{
							Log.Debug(string.Format("No states are mapped to the Lane [{0}]", movedCardEvent.ToLane.Id.Value));
						}
					}
					catch (Exception e)
					{
						string.Format("Error processing moved card, [{0}]: {1}", movedCardEvent.MovedCard.Id, e.Message).Error(e);
					}
				}
			}
			else
			{
				Log.Debug(string.Format("No Card Move Events detected event for board [{0}], exiting method", boardId));
			}

			UpdateBoardVersion(boardId);

		}
		public void SimulateUpdateEvent(long boardId, BoardChangedEventArgs eventArgs, ILeanKitApi api)
		{
			base.BoardUpdate(boardId, eventArgs, api);
		}
		protected override void OnStartTest()
		{
			var api = LeanKitClientFactory.Create(new LeanKitBasicAuth());
			var eventArgs = new BoardChangedEventArgs
			{
				UpdatedCards = new List<CardUpdateEvent> {CardUpdateEvent}
			};
			TestItem.SimulateUpdateEvent(BoardId, eventArgs, api);
		}