/// <summary>
        /// Send the media to the API to update
        /// </summary>
        /// <typeparam name="T">The type of media being sent</typeparam>
        /// <param name="kodiApi">The API object that is going the updating</param>
        /// <param name="mediaId">The id of the media being updated</param>
        /// <param name="resumePositionChanged">Flag indicating if the resume position changed</param>
        /// <param name="watchChanged">Flag indicating if the play count changed</param>
        /// <param name="resumeValue">The new resume value</param>
        /// <param name="watchedValue">The new watched value</param>
        /// <param name="mediaLabel">The display name of the media being updated</param>
        /// <param name="processedFiles">A list keeping track of updated media</param>
        private void UpdateMedia <T>(KodiApi kodiApi, int mediaId, bool resumePositionChanged, bool watchChanged, int resumeValue, int watchedValue, string mediaLabel, List <string> processedFiles)
        {
            string message = "";
            string changes = "";

            switch (kodiApi.UpdateMedia <T>(mediaId, resumePositionChanged, watchChanged, resumeValue, watchedValue))
            {
            case true:
                message = string.Format("{0} updated", mediaLabel);
                Utilities.Message(2, message);
                break;

            default:
                message = string.Format("Fail to update {0}, attempted to", mediaLabel);
                Utilities.Message(2, message);
                break;
            }

            if (watchChanged)
            {
                changes += "Set to watched";
            }
            if (watchChanged && resumePositionChanged)
            {
                changes += ", ";
            }
            if (resumePositionChanged)
            {
                changes += string.Format("Set resume pos to {0}", resumeValue);
            }
            Utilities.Message(3, changes);

            processedFiles.Add(string.Format("[{0}]{1}{2}{3}", DateTime.Now.ToString("yyyyMMdd HH:mm:ss"), message, Environment.NewLine, changes));
        }
        /// <summary>
        /// Read the previously saved media results to from the HDD
        /// </summary>
        /// <param name="kodiApi">The object containing the API details</param>
        /// <returns>The list of media files</returns>
        public void ReadFromHistoricFile(KodiApi kodiApi)
        {
            if (!kodiApi.IsOnline)
            {
                // read the previously saved file media data
                Utilities.Message("Loading {0}'s previously saved media file", kodiApi.Name);
                this.ReadFromFile(kodiApi.ServerIP);

                if (this.HasResults)
                {
                    Utilities.Message(1, "Found {0} movie(s)", this.MovieResult.Result.Count);
                    Utilities.Message(2, "{0} watched", this.MovieResult.Result.CountWatched);
                    Utilities.Message(2, "{0} resumable", this.MovieResult.Result.CountResumable);
                    Utilities.Message(1, "Found {0} episode(s)", this.EpisodeResult.Result.Count);
                    Utilities.Message(2, "{0} watched", this.EpisodeResult.Result.CountWatched);
                    Utilities.Message(2, "{0} resumable", this.EpisodeResult.Result.CountResumable);
                }
                else
                {
                    Utilities.Message(1, "There was an issue serializing the content in {0}", this.FilePath(kodiApi.ServerIP));
                }

                Console.WriteLine("");
            }
        }
        /// <summary>
        /// Update the watched media with the data saved in the JSON files.
        /// </summary>
        /// <param name="otherServerMedia">The other server's media information to compare with</param>
        /// <param name="kodiApi">The object containing the API details</param>
        public void CompareAndUpdateAPIData(ServerMedia otherServerMedia, KodiApi kodiApi)
        {
            List <string> processedFiles = new List <string>();

            if (this.HasResults && otherServerMedia.HasResults)
            {
                Utilities.Message("Syncing {0} watched media", kodiApi.Name);

                // compare and format data
                List <Movie> otherMovies   = otherServerMedia.MovieResult.Result.Movies;
                List <Movie> missingMovies = this.MovieResult.Result.Movies.Except(otherMovies, new KodiMovieEqualityComparer()).ToList();

                foreach (Movie movie in missingMovies)
                {
                    movie.Update(otherMovies, x => x.Label == movie.Label && x.Year == movie.Year);
                }

                this.MismatchMessage <Movie>(missingMovies, "movie", false);

                // send data to API
                foreach (Movie movie in missingMovies.Where(x => x.IsOutDated))
                {
                    this.UpdateMedia <MovieResult>(kodiApi, movie.MovieId, movie.IsOutDatedResumePosition, movie.IsOutDatedWatched, movie.Resume.Position, movie.Playcount, string.Format("{0} {1}", movie.Label, movie.Year), processedFiles);
                }

                // compare and format data
                List <Episode> otherEpisodes   = otherServerMedia.EpisodeResult.Result.Episodes;
                List <Episode> missingEpisodes = this.EpisodeResult.Result.Episodes.Except(otherEpisodes, new KodiEpisodeEqualityComparer()).ToList();

                foreach (Episode episode in missingEpisodes)
                {
                    episode.Update(otherEpisodes, x => x.Label == episode.Label && x.ShowTitle == episode.ShowTitle);
                }

                this.MismatchMessage <Episode>(missingEpisodes, "episode", true);

                // send data to API
                foreach (Episode episode in missingEpisodes.Where(x => x.IsOutDated))
                {
                    this.UpdateMedia <EpisodesResult>(kodiApi, episode.EpisodeId, episode.IsOutDatedResumePosition, episode.IsOutDatedWatched, episode.Resume.Position, episode.Playcount, string.Format("{0} {1}", episode.ShowTitle, episode.Label), processedFiles);
                }
            }
            else if (!this.HasResults)
            {
                Utilities.Message("{0} has no results to sync", kodiApi.Name);
            }
            else if (!otherServerMedia.HasResults)
            {
                Utilities.Message("{0} has no results to sync with", kodiApi.Name);
            }

            // save list of changed values
            if (processedFiles.Count > 0)
            {
                this.SaveLogToFile(processedFiles);
            }
            Console.WriteLine("");
        }
        /// <summary>
        /// Access the API on the server and write the details of the media to the HDD
        /// </summary>
        /// <param name="kodiApi">The API class to use to interact with Kodi</param>
        /// <returns>A object containing the movie and episode data from the API</returns>
        public void ReadFromAPIAndSave(KodiApi kodiApi)
        {
            // make sure server is online
            bool serverOnline = Network.PingHost(kodiApi.ServerIP);

            Utilities.Message("Pinging server {0} {1}", kodiApi.ServerIP, (serverOnline) ? "succeeded" : "failed");

            if (serverOnline)
            {
                kodiApi.SetIsOnline();
                if (kodiApi.IsOnline)
                {
                    Utilities.Message("Reading media on {0} and saving to file", kodiApi.Name);

                    // get the media data from the API
                    this.MovieResult   = kodiApi.GetResults <MovieResult>();
                    this.EpisodeResult = kodiApi.GetResults <EpisodesResult>();

                    // make sure API requests worked
                    if (this.HasResults)
                    {
                        Utilities.Message(1, "Found {0} movie(s)", this.MovieResult.Result.Count);
                        Utilities.Message(2, "{0} watched", this.MovieResult.Result.CountWatched);
                        Utilities.Message(2, "{0} resumable", this.MovieResult.Result.CountResumable);
                        Utilities.Message(1, "Found {0} episode(s)", this.EpisodeResult.Result.Count);
                        Utilities.Message(2, "{0} watched", this.EpisodeResult.Result.CountWatched);
                        Utilities.Message(2, "{0} resumable", this.EpisodeResult.Result.CountResumable);

                        // write the media to the HDD
                        ServerMedia serverMedia = new ServerMedia(this.MovieResult, this.EpisodeResult);

                        string filePath      = serverMedia.FilePath(kodiApi.ServerIP);
                        string directoryPath = new FileInfo(filePath).Directory.FullName;

                        if (!Directory.Exists(directoryPath))
                        {
                            Directory.CreateDirectory(directoryPath);
                            Utilities.Message(1, "Directory {0} created to store media data", directoryPath);
                        }
                        serverMedia.SaveToFile(kodiApi.ServerIP);

                        Utilities.Message(1, "Saved media on {0} to {1}", kodiApi.Name, filePath);
                    }
                    else
                    {
                        Utilities.Message("API request to {0} failed using URL {1}", kodiApi.Name, kodiApi.ServerAPIURL);
                    }
                }
                else
                {
                    Utilities.Message("{0} API is offline", kodiApi.Name);
                }
            }

            Console.WriteLine("");
        }
        public bool UpdateLibraries()
        {
            // set up API objects
            KodiApi kodiApi     = new KodiApi(Settings.Server1Name, Settings.Server1IP, Settings.Server1APIURL, Settings.Server1APIUsername, Settings.Server1APIPassword);
            KodiApi openElecApi = new KodiApi(Settings.Server2Name, Settings.Server2IP, Settings.Server2APIURL, Settings.Server2APIUsername, Settings.Server2APIPassword);

            // read api data and save to file
            ServerMedia kodiMedia = new ServerMedia();

            kodiMedia.ReadFromAPIAndSave(kodiApi);

            ServerMedia openelecMedia = new ServerMedia();

            openelecMedia.ReadFromAPIAndSave(openElecApi);

            // load historical unnwatched API data for inactive APIs
            if (!kodiApi.IsOnline && openElecApi.IsOnline)
            {
                kodiMedia.ReadFromHistoricFile(kodiApi);
            }
            if (kodiApi.IsOnline && !openElecApi.IsOnline)
            {
                openelecMedia.ReadFromHistoricFile(openElecApi);
            }

            // updated libraries based on active APIs
            if (kodiApi.IsOnline)
            {
                kodiMedia.CompareAndUpdateAPIData(openelecMedia, kodiApi);
            }
            if (openElecApi.IsOnline)
            {
                openelecMedia.CompareAndUpdateAPIData(kodiMedia, openElecApi);
            }


            // delay the console window close
            this.DelayConsoleClose(Settings.SecondsBeforeClose);
            return(false);
        }