//! \file       ImagePreview.cs
//! \date       Sun Jul 06 06:34:56 2014
//! \brief      preview images.
//
// Copyright (C) 2014 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//

using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using GARbro.GUI.Strings;
using GARbro.GUI.Properties;
using GameRes;

namespace GARbro.GUI
{
    public partial class MainWindow : Window
    {
        private readonly BackgroundWorker   m_preview_worker = new BackgroundWorker();
        private PreviewFile                 m_current_preview = new PreviewFile();
        private bool                        m_preview_pending = false;

        class PreviewFile
        {
            public string Path { get; set; }
            public string Name { get; set; }
            public Entry  Entry { get; set; }
            public ArcFile Archive { get; set; }

            public bool IsEqual (string path, string name)
            {
                return path.Equals (Path) && name.Equals (Name);
            }
        }

        private void InitPreviewPane ()
        {
            m_preview_worker.DoWork += (s, e) => LoadPreviewImage (e.Argument as PreviewFile);
            m_preview_worker.RunWorkerCompleted += (s, e) => {
                if (m_preview_pending)
                    RefreshPreviewPane();
            };
        }

        /// <summary>
        /// Display entry in preview panel
        /// </summary>
        private void PreviewEntry (Entry entry)
        {
            if (m_current_preview.IsEqual (ViewModel.Path, entry.Name))
                return;
            UpdatePreviewPane (entry);
        }

        void RefreshPreviewPane ()
        {
            m_preview_pending = false;
            var current = CurrentDirectory.SelectedItem as EntryViewModel;
            if (null != current)
                UpdatePreviewPane (current.Source);
            else
                PreviewPane.Source = null;
        }

        void UpdatePreviewPane (Entry entry)
        {
            SetStatusText ("");
            var vm = ViewModel;
            m_current_preview = new PreviewFile { Path = vm.Path, Name = entry.Name };
            PreviewPane.Source = null;
            if (entry.Type != "image")
                return;
            if (vm.IsArchive)
            {
                m_current_preview.Archive = m_app.CurrentArchive;
                m_current_preview.Entry = entry;
            }
            if (!m_preview_worker.IsBusy)
                m_preview_worker.RunWorkerAsync (m_current_preview);
            else
                m_preview_pending = true;
        }

        void LoadPreviewImage (PreviewFile preview)
        {
            try
            {
                Stream file;
                if (null == preview.Archive)
                {
                    string filename = Path.Combine (preview.Path, preview.Name);
                    file = new FileStream (filename, FileMode.Open, FileAccess.Read);
                }
                else
                {
                    file = preview.Archive.OpenEntry (preview.Entry);
                }
                using (file)
                {
                    var data = ImageFormat.Read (file);
                    if (null != data)
                        SetPreviewImage (preview, data.Bitmap);
                    else
                        Trace.WriteLine ("Cannot parse image format", preview.Name);
                }
            }
            catch (Exception X)
            {
                SetStatusText (X.Message);
            }
        }

        void SetPreviewImage (PreviewFile preview, BitmapSource bitmap)
        {
            if (bitmap.DpiX != Desktop.DpiX || bitmap.DpiY != Desktop.DpiY)
            {
                int stride = bitmap.PixelWidth * ((bitmap.Format.BitsPerPixel + 7) / 8); 
                var pixels = new byte[stride*bitmap.PixelHeight];
                bitmap.CopyPixels (pixels, stride, 0);
                var fixed_bitmap = BitmapSource.Create (bitmap.PixelWidth, bitmap.PixelHeight,
                    Desktop.DpiX, Desktop.DpiY, bitmap.Format, bitmap.Palette, pixels, stride);
                bitmap = fixed_bitmap;
            }
            if (!bitmap.IsFrozen)
                bitmap.Freeze();
            Dispatcher.Invoke (() =>
            {
                if (m_current_preview == preview) // compare by reference
                {
                    PreviewPane.Source = bitmap;
                    SetStatusText (string.Format (guiStrings.MsgImageSize, bitmap.PixelWidth,
                                                  bitmap.PixelHeight, bitmap.Format.BitsPerPixel));
                }
            });
        }

        /// <summary>
        /// Fit window size to image.
        /// </summary>
        private void FitWindowExec (object sender, ExecutedRoutedEventArgs e)
        {
            var image = PreviewPane.Source;
            if (null == image)
                return;
            var width = image.Width + Settings.Default.lvPanelWidth.Value + 1;
            var height = image.Height;
            width = Math.Max (ContentGrid.ActualWidth, width);
            height = Math.Max (ContentGrid.ActualHeight, height);
            if (width > ContentGrid.ActualWidth || height > ContentGrid.ActualHeight)
            {
                ContentGrid.Width = width;
                ContentGrid.Height = height;
                this.SizeToContent = SizeToContent.WidthAndHeight;
                Dispatcher.InvokeAsync (() => {
                    this.SizeToContent = SizeToContent.Manual;
                    ContentGrid.Width = double.NaN;
                    ContentGrid.Height = double.NaN;
                }, DispatcherPriority.ContextIdle);
            }
        }

        void ExtractImage (ArcFile arc, Entry entry, ImageFormat target_format)
        {
            using (var file = arc.OpenEntry (entry))
            {
                string source_ext = Path.GetExtension (entry.Name).TrimStart ('.').ToLowerInvariant();
                if (target_format.Extensions.Any (ext => ext == source_ext))
                {
                    // source extension matches target image format, copy file as is
                    using (var output = arc.CreateFile (entry))
                        file.CopyTo (output);
                    return;
                }
                ImageData image = ImageFormat.Read (file);
                if (null == image)
                    throw new InvalidFormatException (string.Format ("{1}: {0}", guiStrings.MsgUnableInterpret, entry.Name));
                string target_ext = target_format.Extensions.First();
                string outname = Path.ChangeExtension (entry.Name, target_ext);
                Trace.WriteLine (string.Format ("{0} => {1}", entry.Name, outname), "ExtractImage");
                using (var outfile = arc.CreateFile (new Entry { Name = outname }))
                {
                    target_format.Write (outfile, image);
                }
            }
        }
    }
}