/// <summary>
        /// Fragt alle Benachrichtigungen ab, die dem Kanal mit der angegebenen Id zugeordnet sind.
        /// Die Abfrage kann durch den Nachrichtennummer Parameter eingeschränkt werden, so dass nur Benachrichtigungen mit
        /// einer höhreren Nachrichtennummer als der angegebenen abgerufen werden.
        /// </summary>
        /// <param name="channelId">Die Id des Kanals, für den die Benachrichtigungen abgefragt werden sollen.</param>
        /// <param name="messageNr">Die Nachrichtennummer, aber der die Nachrichten abgerufen werden sollen.</param>
        /// <returns>Eine Liste von Objekten des Typs Announcement.</returns>
        /// <exception cref="DatabaseException">Wirft DatabaseException, wenn Abruf fehlschlägt.</exception>
        public List<Announcement> GetAnnouncementsOfChannel(int channelId, int messageNr)
        {
            List<Announcement> announcements = new List<Announcement>();

            // Frage das Mutex Objekt ab.
            Mutex mutex = DatabaseManager.GetDatabaseAccessMutexObject();

            // Fordere Zugriff auf die Datenbank an.
            if (mutex.WaitOne(DatabaseManager.MutexTimeoutValue))
            {
                using (SQLiteConnection conn = DatabaseManager.GetConnection())
                {
                    try
                    {
                        string query = @"SELECT * 
                            FROM Message AS m JOIN Announcement AS a ON m.Id=a.Message_Id 
                            WHERE Channel_Id=? AND MessageNumber > ?;";

                        using (var stmt = conn.Prepare(query))
                        {
                            stmt.Bind(1, channelId);
                            stmt.Bind(2, messageNr);

                            while (stmt.Step() == SQLiteResult.ROW)
                            {
                                int id = Convert.ToInt32(stmt["Id"]);
                                string text = (string)stmt["Text"];
                                DateTimeOffset creationDate = DatabaseManager.DateTimeFromSQLite(stmt["CreationDate"].ToString());
                                Priority priority = (Priority)Enum.ToObject(typeof(Priority), stmt["Priority"]);
                                bool read = ((long)stmt["Read"] == 1) ? true : false;
                                int messageNrDB = Convert.ToInt32(stmt["MessageNumber"]);
                                int authorId = Convert.ToInt32(stmt["Author_Moderator_Id"]);
                                string title = (string)stmt["Title"];

                                Announcement announcement = new Announcement(id, text, messageNrDB, creationDate, priority, read, channelId, authorId, title);
                                announcements.Add(announcement);
                            }
                        }
                    }
                    catch (SQLiteException sqlEx)
                    {
                        Debug.WriteLine("GetAnnouncementsOfChannel: SQLiteException occurred. Msg is {0}.", sqlEx.Message);
                        throw new DatabaseException(sqlEx.Message);
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine("GetAnnouncementsOfChannel: Exception occurred. Msg is {0}.", ex.Message);
                        throw new DatabaseException(ex.Message);
                    }
                    finally
                    {
                        mutex.ReleaseMutex();
                    }
                }
            }
            else
            {
                Debug.WriteLine("GetAnnouncementsOfChannel: Mutex timeout.");
                throw new DatabaseException("GetAnnouncementsOfChannel: Timeout: Failed to get access to DB.");
            }

            return announcements;
        }
        /// <summary>
        /// Holt die angegebene Anzahl an aktuellesten Announcements aus der Datenbank. Die aktuellesten
        /// Announcements sind dabei diejenigen, die zeitlich gesehen zuletzt gesendet wurden. Über den Offset 
        /// kann angegeben werden, dass diese Anzahl an Announcements übersprungen werden soll. Das ist für das 
        /// inkrementelle Laden von älteren Announcements wichtig.
        /// </summary>
        /// <param name="channelId">Die Id des Kanals, von dem die Announcements abgerufen werden sollen.</param>
        /// <param name="number">Die Anzahl an Announcements, die abgerufen werden soll.</param>
        /// <param name="offset">Der Offset, der angibt wie viele der neusten Announcements übersprungen werden sollen.</param>
        /// <returns>Eine Liste von Announcement Objekten.</returns>
        /// <exception cref="DatbaseException">Wirft DatabaseException, wenn der Abfruf der Announcements fehlschlägt.</exception>
        public List<Announcement> GetLatestAnnouncements(int channelId, int number, int offset)
        {
            List<Announcement> latestAnnouncements = new List<Announcement>();

            // Frage das Mutex Objekt ab.
            Mutex mutex = DatabaseManager.GetDatabaseAccessMutexObject();

            // Fordere Zugriff auf die Datenbank an.
            if (mutex.WaitOne(DatabaseManager.MutexTimeoutValue))
            {
                using (SQLiteConnection conn = DatabaseManager.GetConnection())
                {
                    try
                    {
                        string query = @"SELECT * 
                            FROM Message AS m JOIN Announcement AS a ON m.Id=a.Message_Id 
                            WHERE Channel_Id=? 
                            ORDER BY a.MessageNumber DESC 
                            LIMIT ? OFFSET ?;";

                        using (var stmt = conn.Prepare(query))
                        {
                            stmt.Bind(1, channelId);
                            stmt.Bind(2, number);
                            stmt.Bind(3, offset);

                            while (stmt.Step() == SQLiteResult.ROW)
                            {
                                int id = Convert.ToInt32(stmt["Id"]);
                                string text = (string)stmt["Text"];
                                DateTimeOffset creationDate = DatabaseManager.DateTimeFromSQLite(stmt["CreationDate"].ToString());
                                Priority priority = (Priority)Enum.ToObject(typeof(Priority), stmt["Priority"]);
                                bool read = ((long)stmt["Read"] == 1) ? true : false;
                                int messageNr = Convert.ToInt32(stmt["MessageNumber"]);
                                int authorId = Convert.ToInt32(stmt["Author_Moderator_Id"]);
                                string title = (string)stmt["Title"];

                                Announcement announcement = new Announcement(id, text, messageNr, creationDate, priority, read, channelId, authorId, title);
                                latestAnnouncements.Add(announcement);
                            }
                        }
                    }
                    catch (SQLiteException sqlEx)
                    {
                        Debug.WriteLine("GetLatestAnnouncements has failed. The message is: {0}.", sqlEx.Message);
                        throw new DatabaseException(sqlEx.Message);
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine("GetLatestAnnouncements has failed. The message is: {0} and stack trace is {1}.",
                            ex.Message, ex.StackTrace);
                        throw new DatabaseException(ex.Message);
                    }
                    finally
                    {
                        mutex.ReleaseMutex();
                    }
                }   // Ende des using Block.
            }
            else
            {
                Debug.WriteLine("Couldn't get access to database. Time out.");
                throw new DatabaseException("Could not get access to the database.");
            }
            
            return latestAnnouncements;
        }
        /// <summary>
        /// Speichere die Daten der gegebenen Announcement in der Datenbank ab.
        /// </summary>
        /// <param name="announcement">Das Announcement Objekt mit den Announcement Daten.</param>
        /// <exception cref="DatabaseException">Wirft eine Exception, wenn die Speicherung fehlschlägt.</exception>
        public void StoreAnnouncement(Announcement announcement)
        {
            if(announcement == null)
            {
                Debug.WriteLine("No valid announcement object passed to the StoreAnnouncement method.");
                return;
            }
            bool successful = true;

            // Frage das Mutex Objekt ab.
            Mutex mutex = DatabaseManager.GetDatabaseAccessMutexObject();

            // Fordere Zugriff auf die Datenbank an.
            if (mutex.WaitOne(DatabaseManager.MutexTimeoutValue))
            {
                using (SQLiteConnection conn = DatabaseManager.GetConnection())
                {
                    try
                    {
                        // Starte eine Transaktion.
                        using (var statement = conn.Prepare("BEGIN TRANSACTION"))
                        {
                            statement.Step();
                        }

                        // Speichere Daten in Message Tabelle.
                        using (var insertMessageStmt = conn.Prepare(@"INSERT INTO Message (Id, Text, CreationDate, Priority, Read) 
                            VALUES (?,?,?,?,?);"))
                        {
                            insertMessageStmt.Bind(1, announcement.Id);
                            insertMessageStmt.Bind(2, announcement.Text);
                            insertMessageStmt.Bind(3, DatabaseManager.DateTimeToSQLite(announcement.CreationDate));
                            insertMessageStmt.Bind(4, (int)announcement.MessagePriority);
                            insertMessageStmt.Bind(5, 0);   // Nachricht noch nicht gelesen.

                            if (insertMessageStmt.Step() != SQLiteResult.DONE)
                            {
                                Debug.WriteLine("StoreAnnouncement: Failed to store message part of announcement with " + 
                                    "id {0}.", announcement.Id);
                                successful = false;
                            }
                        }

                        // Speichere Daten in Announcement Tabelle.
                        using (var insertAnnouncementStmt = conn.Prepare(@"INSERT INTO Announcement (MessageNumber,
                            Channel_Id, Title, Author_Moderator_Id, Message_Id) VALUES (?,?,?,?,?);"))
                        {
                            insertAnnouncementStmt.Bind(1, announcement.MessageNumber);
                            insertAnnouncementStmt.Bind(2, announcement.ChannelId);
                            insertAnnouncementStmt.Bind(3, announcement.Title);
                            insertAnnouncementStmt.Bind(4, announcement.AuthorId);
                            insertAnnouncementStmt.Bind(5, announcement.Id);

                            if (insertAnnouncementStmt.Step() != SQLiteResult.DONE)
                            {
                                Debug.WriteLine("StoreAnnouncement: Failed to store announcement part of announcement with " +
                                    "id {0}.", announcement.Id);
                                successful = false;
                            }
                        }

                        // Commit der Transaktion.
                        if (successful)
                        {
                            using (var statement = conn.Prepare("COMMIT TRANSACTION"))
                            {
                                statement.Step();
                                Debug.WriteLine("StoreAnnouncement: Announcement with id {0} stored.", announcement.Id);
                            }
                        }
                        else
                        {
                            // Rollback der Transaktion.
                            using (var statement = conn.Prepare("ROLLBACK TRANSACTION"))
                            {
                                statement.Step();
                                Debug.WriteLine("StoreAnnouncement: Announcement with id {0} could not be stored. " + 
                                    "Rollback required.", announcement.Id);
                            }
                        }
                    }
                    catch (SQLiteException sqlEx)
                    {
                        Debug.WriteLine("SQLiteException has occurred in StoreAnnouncement. Exception message is: {0}.", sqlEx.Message);
                        // Rollback der Transaktion.
                        using (var statement = conn.Prepare("ROLLBACK TRANSACTION"))
                        {
                            statement.Step();
                        }

                        throw new DatabaseException("Storing announcement data in database has failed.");
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine("Exception has occurred in StoreAnnouncement. " +
                            "Exception message is: {0}, and stack trace is {1}.", ex.Message, ex.StackTrace);
                        // Rollback der Transaktion.
                        using (var statement = conn.Prepare("ROLLBACK TRANSACTION"))
                        {
                            statement.Step();
                        }

                        throw new DatabaseException("Storing announcement data in database has failed.");
                    }
                    finally
                    {
                        mutex.ReleaseMutex();
                    }
                }   // Ende des using Block.        
            }
            else
            {
                Debug.WriteLine("Couldn't get access to database. Time out.");
                throw new DatabaseException("Could not get access to the database.");
            }
        }
        /// <summary>
        /// Führt den Befehl CreateNewAnnouncementCommand aus. Legt eine neue Announcement Nachricht an.
        /// Die Nachricht wird auf dem Server erzeugt und von diesem an alle Abonnenten verteilt.
        /// </summary>
        private async Task executeCreateNewAnnouncementCommand()
        {
            if (SelectedChannel != null)
            {
                displayIndeterminateProgressIndicator();

                // Baue Announcement Objekt mit eingegebenen Daten.
                Priority messagePriority = Priority.NORMAL;
                if (IsMessagePriorityHighSelected)
                {
                    messagePriority = Priority.HIGH;
                }

                Announcement newAnnouncement = new Announcement()
                {
                    Text = AnnouncementContent,
                    Title = AnnouncementTitle,
                    MessagePriority = messagePriority
                };

                try
                {
                    // Starte Anlegen einer neuen Announcement.
                    bool successful = await channelController.CreateAnnouncementAsync(SelectedChannel.Id, newAnnouncement);

                    if (successful && _navService.CanGoBack())
                    {
                        // Gehe zurück auf Detailseite.
                        _navService.GoBack();
                    }
                }
                catch (ClientException ex)
                {
                    displayError(ex.ErrorCode);
                }
                finally
                {
                    hideIndeterminateProgressIndicator();
                }
            }
        }
        /// <summary>
        /// Erzeugt eine neue Nachricht für den Kanal mit der angegebenen Id. 
        /// Die Announcement wird auf dem Server angelegt und dieser verteilt sie an 
        /// alle Abonnenten.
        /// </summary>
        /// <param name="channelId">Die Id des Kanals, für den die Announcement angelegt werden soll.</param>
        /// <param name="newAnnouncement">Ein neues Announcement Objekt.</param>
        /// <returns>Liefert true, wenn die Announcement erfolgreich angelegt werden konnte, sonst false.</returns>
        /// <exception cref="ClientException">Wirft eine ClientException, wenn das Anlegen auf dem Server fehlgeschlagen ist.</exception>
        public async Task<bool> CreateAnnouncementAsync(int channelId, Announcement newAnnouncement)
        {
            if (newAnnouncement == null)
                return false;

            Moderator activeModerator = GetLocalModerator();
            if (activeModerator == null)
            {
                Debug.WriteLine("Moderator not logged in.");
                return false;
            }


            // Führe Validierung der übergebenen Daten durch.
            clearValidationErrors();
            newAnnouncement.ClearValidationErrors();
            newAnnouncement.ValidateAll();
            if (newAnnouncement.HasValidationErrors())
            {
                // Melde Validierungsfehler und breche ab.
                reportValidationErrors(newAnnouncement.GetValidationErrors());
                return false;
            }

            string jsonContent = jsonParser.ParseAnnouncementToJsonString(newAnnouncement);
            if (jsonContent == null)
            {
                Debug.WriteLine("CreateAnnouncementAsync failed, the announcement could " +
                    " not be translated into a json document.");
                return false;
            }

            string serverResponse = null;
            try
            {
                serverResponse = await channelApi.SendCreateAnnouncementRequestAsync(
                    activeModerator.ServerAccessToken,
                    channelId,
                    jsonContent);
            }
            catch (APIException ex)
            {
                if (ex.ErrorCode == ErrorCodes.ChannelNotFound)
                {
                    Debug.WriteLine("Server says channel not found. Channel is probably deleted.");
                    // Behandlung von ChannelNotFound.
                    MarkChannelAsDeleted(channelId);
                }

                Debug.WriteLine("CreateAnnouncement failed. The message is: {0}.", ex.Message);
                // Bilde ab auf ClientException.
                throw new ClientException(ex.ErrorCode, ex.Message);
            }

            // Extrahiere Announcement aus ServerResponse.
            Announcement createdAnnouncement = jsonParser.ParseAnnouncementFromJsonString(serverResponse);

            if (createdAnnouncement != null)
            {
                // Speichere Announcement.
                await StoreReceivedAnnouncementAsync(createdAnnouncement);
            }

            return true;
        }
        /// <summary>
        /// Speichere eine empfangen Announcement ab. Falls notwendig werden fehlende Informationene
        /// (z.B. über den Autor) vom Server geladen.
        /// </summary>
        /// <param name="announcement">Das Announcement Objekt der empfangenen Announcement</param>
        public async Task StoreReceivedAnnouncementAsync(Announcement announcement)
        {
            if(announcement == null)
            {
                return;
            }

            Debug.WriteLine("Trying to store the announcement with id {0} and messageNr {1}.", 
                announcement.Id, announcement.MessageNumber);
            try
            {
                // Prüfe ob der Moderator in der Datenbank existiert, der als Autor der Nachricht eingetragen ist.
                if (moderatorDatabaseManager.IsModeratorStored(announcement.AuthorId))
                {
                    Debug.WriteLine("Starting to store announcement.");
                    // Speichere die Announcement ab.
                    channelDatabaseManager.StoreAnnouncement(announcement);
                }
                else
                {
                    // Fehlerbehandlung.
                    Debug.WriteLine("We do not have the author of the announcement in the database. Cannot store announcement without author reference.");
                    Debug.WriteLine("The missing author has the id {0}.", announcement.AuthorId);

                    Debug.WriteLine("Try querying the responsible moderators from the channel again.");
                    try
                    {
                        List<Moderator> responsibleModerators = await GetResponsibleModeratorsAsync(announcement.ChannelId);
                        if (responsibleModerators != null)
                        {
                            StoreResponsibleModeratorsForChannel(announcement.ChannelId, responsibleModerators);
                        }
                        
                        // Prüfe erneut, ob Eintrag nun vorhanden ist.
                        if (!moderatorDatabaseManager.IsModeratorStored(announcement.AuthorId))
                        {
                             // Bilde auf Dummy-Moderator ab und füge die Announcement ein.
                            Debug.WriteLine("Could not recover from missing author. Set author for announcement with id {0} to the " +
                                "dummy moderator.", announcement.Id);
                            announcement.AuthorId = 0;
                        }

                        // Speichere die Announcement ab.
                        channelDatabaseManager.StoreAnnouncement(announcement);
                    }
                    catch (ClientException ex)
                    {
                        Debug.WriteLine("Request to retrieve missing moderators has failed. Error code is {0}.", ex.ErrorCode);
                        // Bilde auf Dummy-Moderator ab und füge die Announcement ein.
                        Debug.WriteLine("Could not recover from missing author. Set author for announcement with id {0} to the " +
                            "dummy moderator.", announcement.Id);
                        announcement.AuthorId = 0;

                        // Speichere die Announcement ab.
                        channelDatabaseManager.StoreAnnouncement(announcement);
                    }
                    catch (DatabaseException dbEx)
                    {
                        Debug.WriteLine("Exception occurred: " + dbEx.Message);
                        //throw new ClientException(ErrorCodes.LocalDatabaseException, "Local database failure.");
                    }
                } // Ende Fehlerbehandlung.
            }
            catch (DatabaseException ex)
            {
                // Fehler wird nicht weitergereicht, da es sich hierbei um eine Aktion handelt,
                // die normalerweise im Hintergrund ausgeführt wird und nicht aktiv durch den 
                // Nutzer ausgelöst wird.
                Debug.WriteLine("Couldn't store the received announcement of channel. Message was {0}.", ex.Message);
            }
        }
 /// <summary>
 /// Erstellt ein JSON Dokument aus einem Announcement Objekt.
 /// </summary>
 /// <param name="announcement">Das Objekt, das umgewandelt werden soll.</param>
 /// <returns>JSON-Dokument des Objekts, oder null, falls Serialisierung fehlgeschlagen ist.</returns>
 public string ParseAnnouncementToJsonString(Announcement announcement)
 {
     string jsonContent = null;
     try
     {
         jsonContent = JsonConvert.SerializeObject(announcement);
     }
     catch (JsonException jsonEx)
     {
         Debug.WriteLine("JsonParsingManager: Exception during serialization of an announcement object.");
         Debug.WriteLine("JsonParsingManager: Message is: {0}.", jsonEx.Message);
     }
     return jsonContent;
 }