using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Configuration;
using System.Runtime.Remoting.Messaging;
using System.Collections;
using System.IO;
using MyTunes.Library;

namespace MyTunes.Admin
{
    public partial class ScanSongs : System.Web.UI.Page
    {
        private Global _g;

        protected void Page_Load(object sender, EventArgs e)
        {
            PanelUnauthorized.Visible = !User.IsInRole("Managers");
            PanelScan.Visible = !PanelUnauthorized.Visible;
            _g = (Global)HttpContext.Current.ApplicationInstance;
            if (_g.IsScanningActive)
                Response.Redirect("~/Admin/Wait.aspx");
        }

        protected void BtnScanSongs_Click(object sender, EventArgs e)
        {
            _g.AsyncResult = ScanSongsProcess();
            Response.Redirect("Wait.aspx");
        }

        private IAsyncResult ScanSongsProcess()
        {
            string path = ConfigurationManager.AppSettings["MusicPath"];
            ScanSongsDelegate scanDelegate = new ScanSongsDelegate(DoScan);
            return scanDelegate.BeginInvoke(path, new AsyncCallback(ScanCallback), null);
        }

        private delegate void ScanSongsDelegate(string path);

        private void ScanCallback(IAsyncResult ar)
        {
            AsyncResult aResult = (AsyncResult)ar;
            ScanSongsDelegate scanDelegate = (ScanSongsDelegate)aResult.AsyncDelegate;
            scanDelegate.EndInvoke(ar);
        }

        Hashtable mp3Files = null;
        private int songsModified;
        private int songsAdded;
        private int songsFailed;
        private int songsSkipped;
        private int songsFiltered;
        private MyTunesDataContext db;

        private void DoScan(string path)
        {
            try
            {
                db = new MyTunesDataContext();
                _g.IsScanningActive = true;

                songsModified = songsAdded = songsFailed = songsSkipped = songsFiltered = 0;

                // Build the list of MP3 files
                DirectoryInfo dirInfo = new DirectoryInfo(path);
                mp3Files = new Hashtable();
                FindMP3Files(dirInfo);

                // Filter out unchanged files
                FilterSongs();

                // Step 3: For each file, pull the latest tag info
                int iFile = 0;
                foreach (FileInfo file in mp3Files.Values)
                {
                    CheckFile(file, ref iFile);
                }

                _g.ScanResult = string.Format("Scan Completed.  Added: {0}, Updated: {1}, Skipped: {2}, Errors: {3}",
                        songsAdded, songsModified, songsSkipped, songsFailed);
            }
            catch (Exception ex)
            {
                _g.ScanResult = "Error: " + ex.Message;
            }
            finally
            {
                db.Connection.Close();
                db = null;
                _g.IsScanningActive = false;
            }
        }

        private void CheckFile(FileInfo file, ref int iFile)
        {
            _g.ScanStatus =
                    string.Format("Examining file {0} of {1}.  {3} updated, {4} added, {5} errors, {2} skipped, {6} files ignored.",
                            ++iFile,
                            mp3Files.Count,
                            songsFiltered,
                            songsModified,
                            songsAdded,
                            songsFailed,
                            songsSkipped);


            DateTime lastMod = DateTime.Now;
            bool exists = false;

            var songs = from Songs in db.Songs
                        where Songs.URL == file.FullName
                        select Songs;

            if (songs.Count() > 0)
            {
                exists = true;
                lastMod = songs.First().Modified.Value;
            }

            if (lastMod != file.LastWriteTime)
            {
                LoadTags(file, exists);
            }
            else
                songsSkipped++;
        }

        private void LoadTags(FileInfo file, bool exists)
        {
            TagLib.File f;

            try
            {
                f = TagLib.File.Create(file.FullName);
            }
            catch (Exception tagEx)
            {
                _g.ScanErrors.Add(tagEx.Message);
                songsSkipped++;
                return;
            }

            if (f.PossiblyCorrupt ||
                f.Tag.FirstPerformer == null ||
                f.Tag.Title == null)
            {
                _g.ScanErrors.Add(file.FullName + " is corrupt or missing required tags.");
                songsSkipped++;
                return;
            }

            // Here is how to read the embedded album art...
            //
            //if (f.Tag.Pictures.Count() > 0)
            //{
            //    System.Drawing.ImageConverter ic = new System.Drawing.ImageConverter();
            //    System.Drawing.Image img = (System.Drawing.Image)ic.ConvertFrom(f.Tag.Pictures[0]);
            //}


            if (exists)
            {
                UpdateSong(f, file.FullName, file.LastWriteTime);
            }
            else
            {
                AddSong(f, file.FullName, file.LastWriteTime);
            }
        }

        private void AddSong(TagLib.File f, string filename, DateTime lastMod)
        {
            Song s = new Song();

            s.ArtistID = GetArtistID(f.Tag.FirstPerformer);
            s.AlbumID = GetAlbumID(s.ArtistID, f.Tag.Album);
            s.Title = f.Tag.Title;
            s.Track = f.Tag.Track.ToString();
            s.URL = filename;
            s.Added = DateTime.Now;
            s.Modified = lastMod;

            db.Songs.InsertOnSubmit(s);

            try
            {
                db.SubmitChanges();
                songsAdded++;
            }
            catch (Exception songEx)
            {
                _g.ScanErrors.Add(filename + " insert error: " + songEx.Message);
                songsFailed++;
            }
        }

        private void UpdateSong(TagLib.File f, string filename, DateTime lastMod)
        {
            var s = (from Songs in db.Songs
                     where Songs.URL == filename
                     select Songs).First();

            s.ArtistID = GetArtistID(f.Tag.FirstPerformer);
            s.AlbumID = GetAlbumID(s.ArtistID, f.Tag.Album);
            s.Title = f.Tag.Title;
            s.Genre = f.Tag.FirstGenre;
            // The TagLib doesn't expose this directly,
            // but you could
            // string length = f.Tag.SongLength;
            s.Modified = lastMod;

            try
            {
                db.SubmitChanges();
                songsModified++;
            }
            catch (Exception songEx)
            {
                _g.ScanErrors.Add(filename + " update error: " + songEx.Message);
                songsFailed++;
            }
        }

        /// <summary>
        /// Lookup the ID of an album by name.  Adds the
        /// album if it does not yet exist.
        /// </summary>
        /// <param name="artistID">ID of the artist record</param>
        /// <param name="artistName">name of the album (optional)</param>
        /// <returns>the album ID</returns>
        private int GetAlbumID(int artistID, string albumName)
        {
            var albums = from Album in db.Albums
                         where Album.ArtistID == artistID && Album.AlbumName == albumName
                         select Album;

            if (albums.Count() == 0)
            {
                var album = new Album();

                album.ArtistID = artistID;
                album.AlbumName = albumName;

                db.Albums.InsertOnSubmit(album);

                try
                {
                    db.SubmitChanges();
                    return album.AlbumID;
                }
                catch (Exception ex)
                {
                    _g.ScanErrors.Add("Album insert error: " + ex.Message);
                    return -1;
                }
            }
            else
            {
                return albums.First().AlbumID;
            }
        }

        /// <summary>
        /// Lookup the ID of an artist by name.  Adds the
        /// artist if it does not yet exist.
        /// </summary>
        /// <param name="artistName">name of the artist or performer</param>
        /// <returns>the artist ID</returns>
        private int GetArtistID(string artistName)
        {
            var artists = from Artist in db.Artists
                          where Artist.ArtistName == artistName
                          select Artist;
            if (artists.Count() == 0)
            {
                var artist = new Artist();
                artist.ArtistName = artistName;
                db.Artists.InsertOnSubmit(artist);

                try
                {
                    db.SubmitChanges();
                    return artist.ArtistID;
                }
                catch (Exception ex)
                {
                    _g.ScanErrors.Add("Artist insert error: " + ex.Message);
                    return -1;
                }
            }
            else
            {
                return artists.First().ArtistID;
            }
        }

        /// <summary>
        /// Compares list of found MP3 files to the database and
        /// removes any that have not be changed since the last
        /// time the system did a scan.  This results in a lot less
        /// spend time opening files and extracting tag information.
        /// </summary>
        private void FilterSongs()
        {
            _g.ScanStatus = "Filtering out unchanged songs...";

            var songs = from Song in db.Songs
                        select new
                        {
                            Song.URL,
                            Song.Modified
                        };
            foreach (var s in songs)
            {
                if (mp3Files.Contains(s.URL) && ((FileInfo)mp3Files[s.URL]).LastWriteTime == s.Modified)
                {
                    songsFiltered++;
                    mp3Files.Remove(s.URL);
                }
            }
        }
        
        /// <summary>
        /// Recursively processes directories to locate all
        /// files that end with .mp3, then adds them to the
        /// mp3files field for additional processing.
        /// </summary>
        /// <param name="dir"></param>
        private void FindMP3Files(DirectoryInfo dir)
        {
            FileInfo[] files = dir.GetFiles("*.mp3");
            foreach (FileInfo file in files)
            {
                mp3Files.Add(file.FullName, file);
                _g.ScanStatus = String.Format("Searching for MP3 files. {0} found so far...", mp3Files.Count);
            }

            DirectoryInfo[] dirs = dir.GetDirectories();
            foreach (DirectoryInfo d in dirs)
                FindMP3Files(d);
        }

    }
}