﻿/* Copyright (c) 2012 - 2013, Alexandru Geana
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this 
 * list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright notice, this
 * list of conditions and the following disclaimer in the documentation and/or 
 * other materials provided with the distribution.
 * 
 * Neither the name of the Alexandru Geana nor the names of its contributors may
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Shapes;
using Alegen.Libraries.WPF;

namespace Alegen.Winspaces {

    public class Desktop {
        private static volatile bool allowDrop;
        private static Object addRemoveWindowsLock = new Object();
        private CheckedListBox clbControl;
        private bool isActive;

        /// <summary>
        /// Class constructor.
        /// </summary>
        /// <param name="isActive">Initial value for the isActive field.</param>
        public Desktop(bool isActive)
            : base() {
            // Initialize fields.
            this.isActive = isActive;
            this.clbControl = new CheckedListBox();
            // Set up object.
            this.clbControl.ForceCursor = true;
            this.clbControl.ItemCheck += new ItemCheckEventHandler(this.this_ItemCheck);
            // Enable drag&drop functionality
            this.clbControl.AllowDrop = true;
            this.clbControl.GiveFeedback += this.clbControl_GiveFeedback;
            this.clbControl.MouseDown += this.clbControl_MouseDown;
            this.clbControl.DragOver += this.clbControl_DragOver;
            this.clbControl.Drop += this.clbControl_DragDrop;
            // Register newly created object.
            DesktopUtil.RegisterDesktop(this);
        }

        /// <summary>
        /// Method used to set the isActive state of the current Desktop object to true.
        /// This method is synchronized, sets the isActive flag of the last Desktop object to false
        /// and then its own to true.
        /// </summary>
        public void Activate() {
            Mutex switchActiveMtx = new Mutex(false, "switchActiveMtx");
            switchActiveMtx.WaitOne();

            Desktop exActiveDesktop = DesktopUtil.GetActiveDesktop();
            exActiveDesktop.isActive = false;
            exActiveDesktop.HideDesktop();
            this.isActive = true;
            this.ShowDesktop();

            switchActiveMtx.ReleaseMutex();
        }

        /// <summary>
        /// Method used to add a window to the collection of Window objects that the Desktop keeps track of.
        /// Before actually adding the window, some checking is done such as the title not being in the filteredTitles.
        /// </summary>
        /// <param name="hWnd"></param>
        public void AddWindow(IntPtr hWnd) {
            lock (addRemoveWindowsLock) {
                if (!WindowUtil.IsFilteredWindow(hWnd) &&
                    !DesktopUtil.IsTopMostParentManaged(hWnd) &&
                    !DesktopUtil.IsWindowManaged(hWnd) &&
                    !WindowUtil.IsPopUpWindow(hWnd) &&
                    !String.IsNullOrWhiteSpace(UnmanagedCode.GetWindowTextToString(hWnd)))

                    this.clbControl.Dispatcher.Invoke((Action)delegate() {
                        Window w = new Window(hWnd);
                        WindowUtil.SetNonSystemWindowTitle(w.Title);
                        this.clbControl.Items.Add(new CheckedListBoxItem(w, true));
                    });
            }
        }

        /// <summary>
        /// Method used internally to check whether the Desktop object is currently managing the window
        /// pointed to by the hWnd sent as argument.
        /// </summary>
        /// <param name="hWnd">Handle to the window which needs checked.</param>
        /// <returns>True if the window is being managed by this Desktop object and false otherwise</returns>
        public bool IsManagingWindow(IntPtr hWnd) {
            foreach (CheckedListBoxItem clbi in this.clbControl.Items)
                if ((clbi.Item as Window).Handle == hWnd)
                    return true;
            return false;
        }

        /// <summary>
        /// Method used to remove a Window object from the current desktop because it is not active anymore.
        /// </summary>
        /// <param name="hWnd">HANDLE value of the window to be removed.</param>
        public void RemoveWindow(IntPtr hWnd) {
            lock (addRemoveWindowsLock) {
                // Search for the Window object which has the same value for the HANDLE.
                for (int i = 0; i < this.clbControl.Items.Count; i++)
                    // Check if we have found the Window object which we are looking for.
                    if ((this.clbControl.Items[i].Item as Window).Handle == hWnd) {
                        // Delete window.
                        (this.clbControl.Items[i].Item as Window).Delete();
                        // Remove it from the current desktop.
                        this.clbControl.Dispatcher.Invoke((Action)delegate() {
                            this.clbControl.Items.RemoveAt(i);
                        });
                        // End the loop.
                        break;
                    }
            }
        }

        /// <summary>
        /// Method used to show all windows of the current desktop.
        /// </summary>
        public void ShowDesktop() {
            foreach (object clbi in this.clbControl.CheckedItems)
                ((clbi as CheckedListBoxItem).Item as Window).ShowWindow();
            UnmanagedCode.SetForegroundWindow(DesktopUtil.Manager.ApplicationWindowHandle);
        }

        /// <summary>
        /// Method used to hide all windows of the current desktop.
        /// </summary>
        public void HideDesktop() {
            foreach (object clbi in this.clbControl.Items)
                ((clbi as CheckedListBoxItem).Item as Window).HideWindow();
        }

        #region Properties and Indexers
        /// <summary>
        /// Property for isActive field.
        /// </summary>
        public bool IsActive {
            get { return this.isActive; }
            set { this.isActive = value; }
        }

        /// <summary>
        /// Indexer for accessing Window objects.
        /// </summary>
        /// <param name="index">Position of Window to be accessed.</param>
        /// <returns>Window object at position of index.</returns>
        public Window this[int index] {
            get { return (Window)this.clbControl.Items[index].Item; }
        }

        /// <summary>
        /// Property for easy access to the list of Window objects contained in the current desktop.
        /// </summary>
        public List<Window> WindowList {
            get {
                lock (addRemoveWindowsLock) {
                    List<Window> returnList = new List<Window>();
                    foreach (object clbi in this.clbControl.Items)
                        if (clbi is CheckedListBoxItem)
                            returnList.Add(((clbi as CheckedListBoxItem).Item as Window));
                    return returnList;
                }
            }
        }

        /// <summary>
        /// Property which offers a getter for the clbControl field.
        /// </summary>
        public CheckedListBox CheckedListBoxControl {
            get {
                return this.clbControl;
            }
        }
        #endregion

        #region Events
        /// <summary>
        /// Method linked to the ItemCheck event of the current object.
        /// </summary>
        private void this_ItemCheck(object sender, ItemCheckEventArgs e) {
            Window w = (this.clbControl.Items[e.Index].Item as Window);
            if (e.CurrentValue == CheckState.Checked)
                w.HideWindow();
            else {
                if (this.isActive == true) {
                    w.ShowWindow();
                    UnmanagedCode.SetForegroundWindow(DesktopUtil.Manager.ApplicationWindowHandle);
                }
            }
        }

        /// <summary>
        /// Method linked to the GiveFeedback event. It is used to change the cursor during the drag & drop operation.
        /// </summary>
        private void clbControl_GiveFeedback(object sender, GiveFeedbackEventArgs e) {
            e.UseDefaultCursors = false;
            this.clbControl.ForceCursor = true;
            if (allowDrop)
                this.clbControl.Cursor = Cursors.Hand;
            else
                this.clbControl.Cursor = Cursors.No;
        }

        /// <summary>
        /// Method linked to the MouseDown event. It is the method which starts the drag & drop operation.
        /// </summary>
        private void clbControl_MouseDown(object sender, MouseEventArgs e) {
            int index = this.clbControl.IndexFromPoint(e.GetPosition(this.clbControl));
            if (index != -1) {
                Window w = (this.clbControl.Items[index].Item as Window);
                bool isChecked = this.clbControl.CheckedItems.Contains(this.clbControl.Items[index]);
                allowDrop = false;
                DragDrop.DoDragDrop(this.clbControl, new DragDropInformation(this.clbControl, w, isChecked), DragDropEffects.Move);
            }
        }

        /// <summary>
        /// Method linked to the DragOver event. Used for changing the Effect according to where the mouse is located.
        /// In turn, the method above will change the cursor and also the drag & drop operation will fail or succeed.
        /// </summary>
        private void clbControl_DragOver(object sender, DragEventArgs e) {
            DragDropInformation ddi = (e.Data.GetData(typeof(DragDropInformation)) as DragDropInformation);
            if (ddi.Source.Equals(this.clbControl))
                allowDrop = false;
            else
                allowDrop = true;
        }

        /// <summary>
        /// Method linked to the DragDrop event. Triggered the the mouse button is released.
        /// </summary>
        private void clbControl_DragDrop(object sender, DragEventArgs e) {
            this.clbControl.Cursor = Cursors.Arrow;
            if (allowDrop)
                this.clbControl.Dispatcher.Invoke((Action)delegate() {
                    DragDropInformation ddi = (e.Data.GetData(typeof(DragDropInformation)) as DragDropInformation);
                    Window draggedWindow = ddi.DraggedWindow;
                    for(int i = 0; i < ddi.Source.Items.Count; i++)
                        if (ddi.Source.Items[i].Item.Equals(draggedWindow)) {
                            ddi.Source.Items.RemoveAt(i);
                            break;
                        }
                    CheckedListBoxItem clbi = new CheckedListBoxItem(draggedWindow, ddi.IsChecked);
                    this.clbControl.Items.Add(clbi);
                    this.clbControl.Cursor = Cursors.Arrow;
                    if (this.isActive == true) {
                        if (ddi.IsChecked == true) {
                            draggedWindow.ShowWindow();
                            UnmanagedCode.SetForegroundWindow(DesktopUtil.Manager.ApplicationWindowHandle);
                        } else
                            draggedWindow.HideWindow();
                    } else
                        draggedWindow.HideWindow();
                });
        }
        #endregion
    }
}