using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;
using System.Data.SqlClient;
using System.Configuration;
using MyTunes.Common.DAL.Plus;
using MyTunes.Common.DAL.Plus.MyTunesDataSetTableAdapters;
using System.Collections;
using System.Data;
using System.Net.Mail;
using System.Runtime.Remoting.Messaging;
using System.IO;

namespace MyTunes
{
    public class Global : System.Web.HttpApplication
    {
        #region Standard Application Methods
        void Application_Start(object sender, EventArgs e)
        {
            // Code that runs on application startup
            Application["ActiveUsers"] = 0;
        }

        void Application_End(object sender, EventArgs e)
        {
            //  Code that runs on application shutdown
            CleanupUserPlaylistFiles();
        }

        void Application_Error(object sender, EventArgs e)
        {
            // Get the error details 
            HttpException lastErrorWrapper = Server.GetLastError() as HttpException;
            Exception lastError = lastErrorWrapper;
            if (lastErrorWrapper.InnerException != null)
                lastError = lastErrorWrapper.InnerException;

            string lastErrorTypeName = lastError.GetType().ToString();
            string lastErrorMessage = lastError.Message;
            string lastErrorStackTrace = lastError.StackTrace;

            const string Subject = "An Error Has Occurred!";

            // Create the MailMessage object 
            MailMessage mm = new MailMessage(ConfigurationManager.AppSettings["EmailFrom"],
                ConfigurationManager.AppSettings["EmailErrors"]);
            mm.Subject = Subject;
            mm.IsBodyHtml = true;
            mm.Priority = MailPriority.High;
            mm.Body = string.Format(@" 
<html> 
<body> 
  <h1>An Error Has Occurred!</h1> 
  <table cellpadding=""5"" cellspacing=""0"" border=""1""> 
  <tr> 
  <tdtext-align: right;font-weight: bold"">URL:</td> 
  <td>{0}</td> 
  </tr> 
  <tr> 
  <tdtext-align: right;font-weight: bold"">User:</td> 
  <td>{1}</td> 
  </tr> 
  <tr> 
  <tdtext-align: right;font-weight: bold"">Exception Type:</td> 
  <td>{2}</td> 
  </tr> 
  <tr> 
  <tdtext-align: right;font-weight: bold"">Message:</td> 
  <td>{3}</td> 
  </tr> 
  <tr> 
  <tdtext-align: right;font-weight: bold"">Stack Trace:</td> 
  <td>{4}</td> 
  </tr>  
  </table> 
</body> 
</html>",
                Request.RawUrl,
                User.Identity.Name,
                lastErrorTypeName,
                lastErrorMessage,
                lastErrorStackTrace.Replace(Environment.NewLine, "<br />"));


            // Attach the Yellow Screen of Death for this error    
            string YSODmarkup = lastErrorWrapper.GetHtmlErrorMessage();
            if (!string.IsNullOrEmpty(YSODmarkup))
            {
                Attachment YSOD = Attachment.CreateAttachmentFromString(YSODmarkup, "YSOD.htm");
                mm.Attachments.Add(YSOD);
            }


            // Send the email 
            SmtpClient smtp = new SmtpClient();
            smtp.Send(mm);
        }

        void Session_Start(object sender, EventArgs e)
        {
            Application["ActiveUsers"] = ((int)Application["ActiveUsers"]) + 1;
        }

        void Session_End(object sender, EventArgs e)
        {

            // Code that runs when a session ends. 
            // Note: The Session_End event is raised only when the sessionstate mode
            // is set to InProc in the Web.config file. If session mode is set to StateServer 
            // or SQLServer, the event is not raised.

            Application["ActiveUsers"] = Math.Max(0, ((int)Application["ActiveUsers"]) - 1);
        }
        #endregion

        #region MyTunesDataSet related fields, properties and methods
        private static string DATASET_NAME = "MyTunesDataSet";

        CustomerTableAdapter customerAdapter = new CustomerTableAdapter();
        ArtistTableAdapter artistAdapter = new ArtistTableAdapter();
        AlbumTableAdapter albumAdapter = new AlbumTableAdapter();
        SongTableAdapter songAdapter = new SongTableAdapter();
        PlaylistTableAdapter playlistAdapter = new PlaylistTableAdapter();
        PlaylistItemTableAdapter playlistItemAdapter = new PlaylistItemTableAdapter();

        // This structure holds references to Expression
        // columns from the DataSet.  We remove them from
        // the DataSet before loading the records and
        // restore them after the load is complete.
        struct ExpressionColumn
        {
            public DataTable table;
            public DataColumn column;
        }

        private ArrayList removedExpressionColumns;

        /// <summary>
        /// This property is used to access the DataSet.  It tries
        /// to fetch the DataSet from the Cache first and if it is
        /// not found, then it creates a new DataSet, loads the records
        /// and adds it into the Cache for future usage.
        /// </summary>
        public MyTunesDataSet MusicData
        {
            get
            {
                MyTunesDataSet ds = null;

                Application.Lock();

                try
                {
                    ds = (MyTunesDataSet)HttpContext.Current.Cache[DATASET_NAME];
                    if (ds == null)
                    {
                        ds = new MyTunesDataSet();

                        RemoveExpressionColumns(ds);

                        DateTime started = DateTime.Now;

                        ds.Customer.BeginLoadData();
                        customerAdapter.Fill(ds.Customer);
                        ds.Customer.EndLoadData();

                        TimeSpan elapsed = DateTime.Now.Subtract(started);
                        System.Diagnostics.Debug.WriteLine("Customer table loaded in " + elapsed.TotalMilliseconds + " ms");
                        started = DateTime.Now;

                        ds.Artist.BeginLoadData();
                        artistAdapter.Fill(ds.Artist);
                        ds.Artist.EndLoadData();

                        elapsed = DateTime.Now.Subtract(started);
                        System.Diagnostics.Debug.WriteLine("Artist table loaded in " + elapsed.TotalMilliseconds + " ms");
                        started = DateTime.Now;

                        ds.Album.BeginLoadData();
                        albumAdapter.Fill(ds.Album);
                        ds.Album.EndLoadData();

                        elapsed = DateTime.Now.Subtract(started);
                        System.Diagnostics.Debug.WriteLine("Album table loaded in " + elapsed.TotalMilliseconds + " ms");
                        started = DateTime.Now;

                        ds.Song.BeginLoadData();
                        songAdapter.Fill(ds.Song);
                        ds.Song.EndLoadData();

                        elapsed = DateTime.Now.Subtract(started);
                        System.Diagnostics.Debug.WriteLine("Song table loaded in " + elapsed.TotalMilliseconds + " ms");
                        started = DateTime.Now;

                        ds.Playlist.BeginLoadData();
                        playlistAdapter.Fill(ds.Playlist);
                        ds.Playlist.EndLoadData();

                        elapsed = DateTime.Now.Subtract(started);
                        System.Diagnostics.Debug.WriteLine("Playlist table loaded in " + elapsed.TotalMilliseconds + " ms");
                        started = DateTime.Now;

                        ds.PlaylistItem.BeginLoadData();
                        playlistItemAdapter.Fill(ds.PlaylistItem);
                        ds.PlaylistItem.EndLoadData();

                        elapsed = DateTime.Now.Subtract(started);
                        System.Diagnostics.Debug.WriteLine("Playlist items table loaded in " + elapsed.TotalMilliseconds + " ms");
                        started = DateTime.Now;

                        RestoreExpressionColumns();

                        elapsed = DateTime.Now.Subtract(started);
                        System.Diagnostics.Debug.WriteLine("Restoring expression columns took " + elapsed.TotalMilliseconds + " ms");

                        HttpContext.Current.Cache[DATASET_NAME] = ds;
                    }
                    else
                    {
                        System.Diagnostics.Debug.WriteLine("Returning " + DATASET_NAME + " from Application cache instead of reloading database at " + DateTime.Now.ToShortTimeString());
                    }
                }
                catch (Exception ex)
                {
                    // SendErrorMessage("Load Database Failed", ex);
                    System.Diagnostics.Debug.WriteLine("Load Database Failed: " + ex.Message);
                }

                Application.UnLock();

                return ds;
            }

            set
            {
                if (value == null)
                    HttpContext.Current.Cache.Remove(DATASET_NAME);
                else
                    HttpContext.Current.Cache.Insert(DATASET_NAME, value);
            }
        }

        /// <summary>
        /// This function searches all the tables in the DataSet for
        /// Expression columns and removes them.  This speeds up the
        /// loading of the records from the database engine into our
        /// DataSet because then the Expression columns do not need
        /// to reevaluated for each and every record.
        /// 
        /// The Expression columns that are removed are saved in the
        /// removedColumns array so they can restored later.
        /// </summary>
        /// <param name="ds"></param>
        private void RemoveExpressionColumns(DataSet ds)
        {
            removedExpressionColumns = new ArrayList();
            foreach (DataTable table in ds.Tables)
                foreach (DataColumn column in table.Columns)
                    if (column.Expression != null && column.Expression.Length > 0 && table.Columns.CanRemove(column))
                    {
                        ExpressionColumn removed = new ExpressionColumn();
                        removed.table = table;
                        removed.column = column;
                        removedExpressionColumns.Add(removed);
                    }

            foreach (ExpressionColumn removed in removedExpressionColumns)
            {
                System.Diagnostics.Debug.WriteLine("Removing expression column " + removed.column.ColumnName + " from " + removed.table.TableName);
                removed.table.Columns.Remove(removed.column);
            }
        }

        /// <summary>
        /// Removes all locally defined dataset expressions to speed
        /// up data load.  Saves removed columns for later restoration.
        /// </summary>
        /// <param name="table">The DataTable to be processed</param>
        private void RemoveExpressionColumns(DataTable table)
        {
            removedExpressionColumns = new ArrayList();
            foreach (DataColumn column in table.Columns)
                if (column.Expression != null && column.Expression.Length > 0 && table.Columns.CanRemove(column))
                {
                    ExpressionColumn removed = new ExpressionColumn();
                    removed.table = table;
                    removed.column = column;
                    removedExpressionColumns.Add(removed);
                }

            foreach (ExpressionColumn removed in removedExpressionColumns)
            {
                System.Diagnostics.Debug.WriteLine("Removing expression column " + removed.column.ColumnName + " from " + removed.table.TableName);
                removed.table.Columns.Remove(removed.column);
            }
        }

        /// <summary>
        /// Adds any Expression columns that were removed from the
        /// DataSet and saved into the removedColumns array by the
        /// RemoveExpressionColumns function.
        /// </summary>
        private void RestoreExpressionColumns()
        {
            foreach (ExpressionColumn removed in removedExpressionColumns)
            {
                System.Diagnostics.Debug.WriteLine("Restoring expression column " + removed.column.ColumnName + " in " + removed.table.TableName);
                removed.table.Columns.Add(removed.column);
            }
            // Mark ArrayList as eligible for garbage collection
            removedExpressionColumns = null;
        }

        #endregion

        #region Quick Link Support
        private static string ARTIST_LINKS = "ArtistQuickLinks";
        private static string ALBUM_LINKS = "AlbumQuickLinks";
        private static string SONG_LINKS = "SongQuickLinks";

        /// <summary>
        /// Returns a string with links to jump to artists by letter.
        /// </summary>
        public string ArtistQuickLinks
        {
            get
            {
                string links = (string)HttpContext.Current.Cache[ARTIST_LINKS];
                if (links == null)
                {
                    links = BuildQuickLinks("ArtistsCached.aspx", "artist", "artist");
                    HttpContext.Current.Cache[ARTIST_LINKS] = links;
                    System.Diagnostics.Debug.WriteLine("Creating new artist quick links...");
                }
                else
                {
                    System.Diagnostics.Debug.WriteLine("Fetched artist quick links from cache...");
                }
                return links;
            }
        }

        /// <summary>
        /// Returns a string with links to jump to albums by letter.
        /// </summary>
        public string AlbumQuickLinks
        {
            get
            {
                string links = (string)HttpContext.Current.Cache[ALBUM_LINKS];
                if (links == null)
                {
                    links = BuildQuickLinks("AlbumsCached.aspx", "album", "album");
                    HttpContext.Current.Cache[ALBUM_LINKS] = links;
                    System.Diagnostics.Debug.WriteLine("Creating new album quick links...");
                }
                else
                {
                    System.Diagnostics.Debug.WriteLine("Fetched album quick links from cache...");
                }
                return links;
            }
        }

        /// <summary>
        /// Returns a string with links to jump to songs by letter.
        /// </summary>
        public string SongQuickLinks
        {
            get
            {
                string links = (string)HttpContext.Current.Cache[SONG_LINKS];
                if (links == null)
                {
                    links = BuildQuickLinks("SongsCached.aspx", "song", "title");
                    HttpContext.Current.Cache[SONG_LINKS] = links;
                    System.Diagnostics.Debug.WriteLine("Creating new song quick links...");
                }
                else
                {
                    System.Diagnostics.Debug.WriteLine("Fetched song quick links from cache...");
                }
                return links;
            }
        }

        /// <summary>
        /// Helper function that creates the quick link strings for a
        /// table.
        /// 
        /// NOTE: Uses connected mode for speed, since the SQL Server can
        /// retrieve substrings, which DataSets do not support.
        /// </summary>
        /// <param name="script"></param>
        /// <param name="table"></param>
        /// <param name="column"></param>
        /// <returns></returns>
        private string BuildQuickLinks(string script, string table, string column)
        {
            SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["MyTunesConnectionString"].ConnectionString);

            conn.Open();

            SqlCommand cmd = conn.CreateCommand();
            cmd.CommandText = "SELECT DISTINCT SUBSTRING(" + column + ",1,1) FROM " + table + " ORDER BY SUBSTRING(" + column + ",1,1)";
            SqlDataReader reader = cmd.ExecuteReader();
            string links = "<a href='" + HttpUtility.UrlEncode(script) + "'>All</a>";

            while (reader.Read())
            {
                string letter = reader[0].ToString().ToUpper();
                links += " | <a href='" + HttpUtility.UrlEncode(script) + "?letter=" +
                    HttpUtility.UrlEncode(letter) + "'>" + HttpUtility.HtmlEncode(letter) + "</a>";
            }

            reader.Close();

            return links;
        }
        #endregion

        #region Background Scanning Support
        private IAsyncResult _asyncResult;
        private bool _scanningActive = false;
        private string _scanStatus;
        private string _scanResults;
        private List<string> _scanErrors = new List<string>();

        public IAsyncResult AsyncResult
        {
            get
            {
                lock (this)
                {
                    return _asyncResult;
                }
            }
            set
            {
                lock (this)
                {
                    _asyncResult = value;
                }
            }
        }

        public bool IsScanningActive
        {
            get
            {
                lock (this)
                {
                    return _scanningActive;
                }
            }
            set
            {
                lock (this)
                {
                    if (!_scanningActive && value)
                        _scanErrors.Clear();
                    _scanningActive = value;
                }
            }
        }

        public string ScanStatus
        {
            get
            {
                lock (this)
                {
                    return _scanStatus;
                }
            }
            set
            {
                lock (this)
                {
                    _scanStatus = value;
                }
            }
        }

        public string ScanResult
        {
            get
            {
                lock (this)
                {
                    return _scanResults;
                }
            }
            set
            {
                lock (this)
                {
                    _scanResults = value;
                }
            }
        }

        public List<String> ScanErrors
        {
            get
            {
                lock(this)
                {
                    return _scanErrors;
                }
            }
        }

        #endregion

        #region Playlist Cleanup Support
        private List<string> _userPlaylistFiles = new List<string>();
        public void TrackUserPlaylistFile(string filename)
        {
            if (!_userPlaylistFiles.Contains(filename))
                _userPlaylistFiles.Add(filename);
        }

        private void CleanupUserPlaylistFiles()
        {
            try
            {
                foreach (string filename in _userPlaylistFiles)
                {
                    FileInfo fi = new FileInfo(filename);
                    if (fi.Exists)
                        fi.Delete();
                    fi.Directory.Refresh();
                    if (fi.Directory.GetFiles().Count() == 0)
                        fi.Directory.Delete();
                }
            }
            catch (Exception)
            {
            }
        }
        #endregion
    }
}
