using C1.Xaml.RichTextBox.Documents;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238

namespace RichTextBoxSave
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            c1RichTextBox1.DocumentChanged += c1RichTextBox1_DocumentChanged;
        }

        void c1RichTextBox1_DocumentChanged(object sender, EventArgs e)
        {
            c1RichTextBox1.DocumentHistory.HistoryChanged += DocumentHistory_HistoryChanged;
        }

        void DocumentHistory_HistoryChanged(object sender, EventArgs e)
        {
            TextStatus.Text = "File Modified";
        }

        private async void btnOpen_Click(object sender, RoutedEventArgs e)
        {
            // open file picker
            FileOpenPicker openPicker = new FileOpenPicker();
            openPicker.FileTypeFilter.Add(".html");
            openPicker.FileTypeFilter.Add(".htm");
            openPicker.FileTypeFilter.Add(".rtf");
            StorageFile file = await openPicker.PickSingleFileAsync();
            if (file != null)
            {
                if (file.FileType.Equals(".html") || file.FileType.Equals(".htm"))
                {
                    // Open HTML and set to C1RichTextBox
                    try
                    {
                        c1RichTextBox1.SetHtml(await ReadFileText(file), null);
                    }
                    catch
                    {
                        c1RichTextBox1.Text = "Error loading HTML file.";
                        return;
                    }
                }
                else if (file.FileType.Equals(".rtf"))
                {
                    // Open RTF and set to C1RichTextBox
                    try
                    {
                        c1RichTextBox1.Document = new RtfFilter().ConvertToDocument(await ReadFileText(file));
                    }
                    catch
                    {
                        c1RichTextBox1.Text = "Error loading RTF file.";
                        return;
                    }
                }
                TextStatus.Text = "File Opened";
            } 
        }

        private async void btnSave_Click(object sender, RoutedEventArgs e)
        {
            FileSavePicker savePicker = new FileSavePicker();
            savePicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
            savePicker.FileTypeChoices.Add("HTML Page (*.html)", new List<string>() { ".html", ".htm" });
            savePicker.FileTypeChoices.Add("Rich Text Format (*.rtf)", new List<string>() { ".rtf" });
            savePicker.DefaultFileExtension = ".html";
            StorageFile file = await savePicker.PickSaveFileAsync();
            if (file != null)
            {
                if (file.FileType.Equals(".html") || file.FileType.Equals(".htm"))
                {
                    // Save HTML
                    await FileIO.WriteTextAsync(file, c1RichTextBox1.GetHtml(HtmlEncoding.StyleSheet));
                }
                else if (file.FileType.Equals(".rtf"))
                {
                    // Save RTF
                    await FileIO.WriteTextAsync(file, new RtfFilter().ConvertFromDocument(c1RichTextBox1.Document));
                }
                TextStatus.Text = "File Saved";
            }
        }

        /// <summary>
        /// Handles reading file contents considering encoding types and returns it as a string
        /// </summary>
        /// <returns></returns>
        private async Task<string> ReadFileText(StorageFile file)
        {
            string text = null;
            if (file != null)
            {
                // read content into buffer
                var buffer = await Windows.Storage.FileIO.ReadBufferAsync(file);
                using (DataReader dataReader = DataReader.FromBuffer(buffer))
                {
                    // get bytes from buffer
                    var data = new byte[buffer.Length];
                    dataReader.ReadBytes(data);
                    // convert bytes into string

                    if (data.Length > 1 && data[0] == 0xff && data[1] == 0xfe) // little-endian Unicode
                    {
                        text = Encoding.Unicode.GetString(data, 0, data.Length);
                    }
                    else if (data.Length > 1 && data[0] == 0xfe && data[1] == 0xff) // big-endian Unicode
                    {
                        text = Encoding.BigEndianUnicode.GetString(data, 0, data.Length);
                    }
                    else
                    {
                        Encoding encoding;
                        try
                        {
                            encoding = GetEncoding(data);
                            if(encoding == null)
                            {
                                // should work for most non-unicode files (ASCII, 1252, etc - but not UTF7)
                                text = Encoding.UTF8.GetString(data, 0, data.Length);
                            }
                            else
                            {
                                text = encoding.GetString(data, 0, data.Length);
                            }                            
                        }
                        catch
                        {
                            encoding = Encoding.GetEncoding("Windows-1252");
                            text = encoding.GetString(data, 0, data.Length);
                        }
                    }
                }
            }
            return text;
        }

        Encoding GetEncoding(byte[] bytes)
        {
            const byte bEscape = 0x1B;
            const byte bAt = 0x40;
            const byte bDollar = 0x24;
            const byte bAnd = 0x26;
            const byte bOpen = 0x28;    //'('
            const byte bB = 0x42;
            const byte bD = 0x44;
            const byte bJ = 0x4A;
            const byte bI = 0x49;

            int len = bytes.Length;
            byte b1, b2, b3, b4;
            bool isBinary = false;
            for (int i = 0; i < len; i++)
            {
                b1 = bytes[i];
                if (b1 <= 0x06 || b1 == 0x7F || b1 == 0xFF)
                {
                    //'binary'
                    isBinary = true;
                    if (b1 == 0x00 && i < len - 1 && bytes[i + 1] <= 0x7F)
                    {
                        //smells like raw unicode
                        return null;
                    }
                }
            }
            if (isBinary)
            {
                return null;
            }

            //not Japanese
            bool notJapanese = true;
            for (int i = 0; i < len; i++)
            {
                b1 = bytes[i];
                if (b1 == bEscape || 0x80 <= b1)
                {
                    notJapanese = false;
                    break;
                }
            }
            if (notJapanese)
            {
                return null;
            }

            for (int i = 0; i < len - 2; i++)
            {
                b1 = bytes[i];
                b2 = bytes[i + 1];
                b3 = bytes[i + 2];

                if (b1 == bEscape)
                {
                    if (b2 == bDollar && b3 == bAt)
                    {
                        //JIS_0208 1978
                        //JIS
                        return Encoding.GetEncoding("iso-2022-jp");
                    }
                    else if (b2 == bDollar && b3 == bB)
                    {
                        //JIS_0208 1983
                        //JIS
                        return Encoding.GetEncoding("iso-2022-jp");
                    }
                    else if (b2 == bOpen && (b3 == bB || b3 == bJ))
                    {
                        //JIS_ASC
                        //JIS
                        return Encoding.GetEncoding("iso-2022-jp");
                    }
                    else if (b2 == bOpen && b3 == bI)
                    {
                        //JIS_KANA
                        //JIS
                        return Encoding.GetEncoding("iso-2022-jp");
                    }
                    if (i < len - 3)
                    {
                        b4 = bytes[i + 3];
                        if (b2 == bDollar && b3 == bOpen && b4 == bD)
                        {
                            //JIS_0212
                            //JIS
                            return Encoding.GetEncoding("iso-2022-jp");
                        }
                        if (i < len - 5 &&
                            b2 == bAnd && b3 == bAt && b4 == bEscape &&
                            bytes[i + 4] == bDollar && bytes[i + 5] == bB)
                        {
                            //JIS_0208 1990
                            //JIS
                            return Encoding.GetEncoding("iso-2022-jp");
                        }
                    }
                }
            }

            //should be euc|sjis|utf8
            int sjis = 0;
            int euc = 0;
            int utf8 = 0;
            for (int i = 0; i < len - 1; i++)
            {
                b1 = bytes[i];
                b2 = bytes[i + 1];
                if (((0x81 <= b1 && b1 <= 0x9F) || (0xE0 <= b1 && b1 <= 0xFC)) &&
                    ((0x40 <= b2 && b2 <= 0x7E) || (0x80 <= b2 && b2 <= 0xFC)))
                {
                    //SJIS_C
                    sjis += 2;
                    i++;
                }
            }
            for (int i = 0; i < len - 1; i++)
            {
                b1 = bytes[i];
                b2 = bytes[i + 1];
                if (((0xA1 <= b1 && b1 <= 0xFE) && (0xA1 <= b2 && b2 <= 0xFE)) ||
                    (b1 == 0x8E && (0xA1 <= b2 && b2 <= 0xDF)))
                {
                    //EUC_C
                    //EUC_KANA
                    euc += 2;
                    i++;
                }
                else if (i < len - 2)
                {
                    b3 = bytes[i + 2];
                    if (b1 == 0x8F && (0xA1 <= b2 && b2 <= 0xFE) &&
                        (0xA1 <= b3 && b3 <= 0xFE))
                    {
                        //EUC_0212
                        euc += 3;
                        i += 2;
                    }
                }
            }
            for (int i = 0; i < len - 1; i++)
            {
                b1 = bytes[i];
                b2 = bytes[i + 1];
                if ((0xC0 <= b1 && b1 <= 0xDF) && (0x80 <= b2 && b2 <= 0xBF))
                {
                    //UTF8
                    utf8 += 2;
                    i++;
                }
                else if (i < len - 2)
                {
                    b3 = bytes[i + 2];
                    if ((0xE0 <= b1 && b1 <= 0xEF) && (0x80 <= b2 && b2 <= 0xBF) &&
                        (0x80 <= b3 && b3 <= 0xBF))
                    {
                        //UTF8
                        utf8 += 3;
                        i += 2;
                    }
                }
            }
            if (euc > sjis && euc > utf8)
            {
                //EUC
                return Encoding.GetEncoding("euc-jp");
            }
            else if (sjis > euc && sjis > utf8)
            {
                //SJIS
                return Encoding.GetEncoding("shift_jis");
            }
            else if (utf8 > euc && utf8 > sjis)
            {
                //UTF8
                return null;
            }
            return null;
        }
    }
}
