// $Id: PPTDeckIO.cs 1881 2009-06-08 20:01:08Z cmprince $

using System;
using PowerPoint = Microsoft.Office.Interop.PowerPoint;
using Core = Microsoft.Office.Core;
using PPTLibrary;
using PPTPaneManagement;
using UW.ClassroomPresenter.Model.Presentation;
using System.Security.Cryptography;
using System.Threading;
using System.IO;
using System.CodeDom.Compiler;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Runtime.Serialization.Formatters.Binary;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Text;
using UW.ClassroomPresenter.Model.Background;
using UW.ClassroomPresenter.Viewer.Background;

namespace UW.ClassroomPresenter.Decks {


    /// <summary>
    /// Summary description for PPTDeckIO.
    /// </summary>
    public class PPTDeckIO {

        #region OpenPPT Methods

        public class PPTNotInstalledException : ApplicationException {
            public PPTNotInstalledException(string message, Exception innerException) : base(message, innerException) { }
        }
        public class PPTFileOpenException : ApplicationException {
            public PPTFileOpenException(string message, Exception innerException) : base(message, innerException) { }
        }
        public class PPTFileSaveException : ApplicationException {
            public PPTFileSaveException(string message, Exception innerException) : base(message, innerException) { }
        }

        public static DeckModel OpenPPT(FileInfo file, Form form) {
            DoWorkEventArgs args = new DoWorkEventArgs(form);
            return PPTDeckIO.OpenPPT(file, null, args);
        }

        public static DeckModel OpenPPT(FileInfo file, BackgroundWorker worker, DoWorkEventArgs progress) {
            //Start the progress bar
            if (worker != null) {
                worker.ReportProgress(0, "  Initializing...");
            }

            OpenDeckDialog.OpenArgs args = (progress != null ? progress.Argument as OpenDeckDialog.OpenArgs : null);
            Form form = (args != null ? args.Form : Viewer.ViewerForm.ActiveForm);

            //Try to detect if powerpoint is already running (powerpnt.exe)
            bool pptAlreadyRunning = false;
            Process[] processes = Process.GetProcesses();
            for (int i = 0; i < processes.Length; i++) {
                string currentProcess = processes[i].ProcessName.ToLower();
                if (currentProcess == "powerpnt") {
                    pptAlreadyRunning = true;
                    break;
                }
            }
            //Open PowerPoint + open file
            PowerPoint.Application pptapp;
            try {
                pptapp = new PowerPoint.Application();
            }
            catch (Exception e) {
                throw new PPTNotInstalledException("Failed to create PowerPoint Application.  See InnerException for details.", e);
            }

            PowerPoint._Presentation presentation;
            try {
                presentation = pptapp.Presentations.Open(file.FullName, Core.MsoTriState.msoTrue, Core.MsoTriState.msoFalse, Core.MsoTriState.msoFalse);
            }
            catch (Exception e) {
                throw new PPTFileOpenException("Failed to open PowerPoint file.  See InnerException for details.", e);
            }

            //Initialize the PPT Shape tag reader
            PPTPaneManagement.PPTPaneManager pptpm = new PPTPaneManagement.PPTPaneManager();

            //Create a new DeckModel
            Guid deckGuid = Guid.Empty;
            try {
                string g = presentation.Tags["WEBEXPORTGUID"];
                if (g == "") {
                    deckGuid = Guid.NewGuid();
                }
                else {
                    deckGuid = new Guid(g);
                }
            }
            catch {
                deckGuid = Guid.NewGuid();
            }
            DeckModel deck = new DeckModel(deckGuid, DeckDisposition.Empty, file.Name);

            //Initialize a temporary file collection that will be where slide images are exported to
            TempFileCollection tempFileCollection = new TempFileCollection();
            string dirpath = tempFileCollection.BasePath;
            if (!Directory.Exists(dirpath)) {
                Directory.CreateDirectory(dirpath);
            } else {
                Directory.Delete(dirpath, true);
                Directory.CreateDirectory(dirpath);
            }

            //Lock it
            using(Synchronizer.Lock(deck.SyncRoot)) {

                // set background color
                int bgr = presentation.SlideMaster.Background.Fill.ForeColor.RGB;
                deck.DeckBackgroundColor = Color.FromArgb(0xff, bgr & 0xff, (bgr >> 8) & 0xff, (bgr >> 16) & 0xff);
                
                //Iterate over all slides
                for (int i = 1;  i <= presentation.Slides.Count; i++) {
                    if (progress != null && progress.Cancel)
                        break;

                    //Increment the ProgressBarForm
                    if (worker != null)
                        worker.ReportProgress((i * 100) / (presentation.Slides.Count+1), "  Reading slide " + i + " of " + presentation.Slides.Count);

                    //Get the slide
                    PowerPoint._Slide currentSlide = presentation.Slides[i];

                    if (currentSlide.SlideShowTransition.Hidden == Core.MsoTriState.msoTrue)
                        continue;

                    SlideModel newSlideModel = CreateSlide(form, presentation.PageSetup, pptpm, deck, tempFileCollection, dirpath, currentSlide);
                    using (Synchronizer.Lock(newSlideModel.SyncRoot))
                    {
                        newSlideModel.Origin = i;
                    }

                    //Create a new Entry + reference SlideModel
                    TableOfContentsModel.Entry newEntry = new TableOfContentsModel.Entry(Guid.NewGuid(), deck.TableOfContents, newSlideModel);
                    
                    //Lock the TOC
                    using(Synchronizer.Lock(deck.TableOfContents.SyncRoot)) {
                        //Add Entry to TOC
                        deck.TableOfContents.Entries.Add(newEntry);
                    }
                    
                }
                
                deck.Filename = file.FullName;

            }
            //Close the presentation
            presentation.Close();
            presentation = null;
            //If PowerPoint was not open before, close PowerPoint
            if (!pptAlreadyRunning) {
                pptapp.Quit();
                pptapp = null;
            }
            GC.Collect();
            //Delete temp directory
            tempFileCollection.Delete();
            Directory.Delete(dirpath);

            if (worker != null)
                worker.ReportProgress(100, " Done!");

            //Return the deck
            if (progress != null)
                progress.Result = deck;
            return deck;
        }

        #endregion

        #region importing powerpoint

        private static ImageSheetModel ExportSlide(string dirpath, TempFileCollection tempFileCollection, int slideWidth, int slideHeight,
            DeckModel deck, PowerPoint._Slide currentSlide, SheetDisposition disp, int animLayer)
        {
            // exporting with WMF or EMF doesn't give good results: the resulting images are scalable, but the kearning and positioning is all wrong,
            // and thin lines look terrible
            string filename = PPTDeckIO.GenerateFilename(dirpath, ".png");
            currentSlide.Export(filename, "PNG", 1400, 1050); // kwalsh: should match second display (projector) resolution
            tempFileCollection.AddFile(filename, false);
            //Compute the MD5 of the layer
            FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read);
            MD5 md5Provider = new MD5CryptoServiceProvider();
            byte[] md5 = md5Provider.ComputeHash(fs);
            fs.Seek(0, SeekOrigin.Begin);
            Image image = Image.FromStream(fs);
            fs.Close();      
            deck.AddSlideContent((ByteArray)md5, image); //Add the Image+MD5 to the deck
            return new ImageSheetModel(deck, Guid.NewGuid(), disp, animLayer,
                new Rectangle(0, 0, slideWidth, slideHeight), (ByteArray)md5, 1 + animLayer);
        }


        struct Effect
        {
            public Boolean appear;
            public PowerPoint.Shape shape;
            public int para;
            public string text;
            public int bullet;
        }

        private static void MakeVisible(Effect e, Boolean appear)
        {
            for (int retry = 0; retry < 3; retry++)
            {
                try
                {
                    // todo: not just the single paragraph, might also be sub-paragraphs
                    if (e.para <= 0)
                    {
                        e.shape.Visible = (appear ? Core.MsoTriState.msoTrue : Core.MsoTriState.msoFalse);
                    }
                    else if (appear)
                    {
                        PowerPoint.TextRange para = e.shape.TextFrame.TextRange.Paragraphs(e.para);
                        int j = 0;
                        foreach (PowerPoint.TextRange line in para.Lines())
                        {
                            for (int i = 1; i < line.Characters().Count; i++)
                            {
                                line.Characters(i).Text = e.text.Substring(j++, 1);
                            }
                            j++;
                        }
                        if (e.bullet != 0)
                            para.ParagraphFormat.Bullet.Character = e.bullet;
                    }
                    else
                    {
                        PowerPoint.TextRange para = e.shape.TextFrame.TextRange.Paragraphs(e.para);
                        foreach (PowerPoint.TextRange line in para.Lines())
                        {
                            for (int i = 1; i < line.Characters().Count; i++)
                            {
                                line.Characters(i).Text = " ";
                            }
                        }
                        if (e.bullet != 0)
                            para.ParagraphFormat.Bullet.Character = 160; // unicode space
                    }
                    return;
                }
                catch (Exception err)
                {
                    Debug.Print(err.ToString());
                }
            }
        }

        private static List<List<Effect>> Effects(PowerPoint.Sequence seq)
        {
            Debug.Print("{0} steps in main animation timeline", seq.Count);
            // go backwards through all animation effects, building the effect lists and hiding anything not initially visible
            List<List<Effect>> effects = new List<List<Effect>>();
            List<Effect> step = new List<Effect>();
            for (int i = seq.Count; i > 0; i--)
            {
                PowerPoint.Effect eff = seq[i];
                Effect e;
                e.appear = (eff.Exit != Core.MsoTriState.msoTrue);
                e.shape = eff.Shape;
                e.text = "";
                e.para = 0;
                e.bullet = 0;
                try {
                    if (eff.Paragraph > 0)
                    {
                        eff.Shape.TextFrame.AutoSize = PowerPoint.PpAutoSize.ppAutoSizeNone;
                        // replace auto wrapping with manual wrapping on every line
                        PowerPoint.TextRange para = eff.Shape.TextFrame.TextRange.Paragraphs(eff.Paragraph, 1);
                        for (int line = 1; line <= para.Lines().Count; line++)
                        {
                            char eol = para.Lines(line).Characters(para.Lines(line).Characters().Count).Text[0];
                            if (eol != '\r' && eol != '\n' && eol != '\v')
                                para.Lines(line).InsertAfter("\v");
                        }
                        // save the text in case it later needs to appear
                        e.text = para.Text;
                        e.para = eff.Paragraph;
                        if (para.ParagraphFormat.Bullet.Type != PowerPoint.PpBulletType.ppBulletNone)
                            e.bullet = para.ParagraphFormat.Bullet.Character;
                    }
                } catch (System.Runtime.InteropServices.COMException) {
                    e.para = 0;
                }
                step.Add(e);
                if (eff.Timing.TriggerType == PowerPoint.MsoAnimTriggerType.msoAnimTriggerOnPageClick)
                {
                    foreach (Effect e2 in step) 
                    {
                        // rewind this step so it can be replayed later
                        MakeVisible(e2, !e2.appear);
                    }
                    step.Reverse();
                    effects.Add(step);
                    step = new List<Effect>();
                }
            }

            step.Reverse();
            effects.Add(step);
            effects.Reverse();
            return effects;
        }

        /// <summary>
        /// Create a slide model from a powerpoint slide
        /// </summary>
        /// <param name="pageSetup"></param>
        /// <param name="pptpm"></param>
        /// <param name="deck"></param>
        /// <param name="tempFileCollection"></param>
        /// <param name="dirpath"></param>
        /// <param name="currentSlide"></param>
        /// <returns></returns>
        private static SlideModel CreateSlide(Form form, PowerPoint.PageSetup pageSetup, PPTPaneManagement.PPTPaneManager pptpm, DeckModel deck, TempFileCollection tempFileCollection, string dirpath, PowerPoint._Slide currentSlide) {
            int slideWidth = (int)pageSetup.SlideWidth;     //Standard = 720  => 6000
            int slideHeight = (int)pageSetup.SlideHeight;   //Standard = 540  => 4500

            // kwalsh: old strategy:
            // 1: make all shapes invisible
            // 2: slide.export(), use resulting image as background
            // 3: for each contiguous set of similar-disposition shapes
            //    shaperange.export(PpRelaiveToSlide), use resulting image as another sheet
            // 4: paint background, then paint all sheets with matching disposition
            //
            // kwalsh: animation attempt 1:
            //    same, but change disposition to be a disposition-animationlayer pair
            //
            // problem: alignment sucks (some shapes aren't placed quite exactly correctly, because
            // it is hard to recreate powerpoint's layout algorithm)
            //
            // kwalsh: new strategy
            // set visibleShapes = emptyset
            // for each animation step:
            //    1: advance timeline, adding and removing from visibleShapes as needed
            //    2: mark as visible only things in visibleShapes with disposition All
            //    3: export and add image as disposition All
            //    4: if there are things in visileShapes with disposition Instructor
            //         then mark them as visible, export, and add image as disposition Instructor
            // when painting, paint only the highest layer allowable disposition sheet

            Dictionary<PowerPoint.Shape, SheetDisposition> shapeDisposition = new Dictionary<PowerPoint.Shape, SheetDisposition>();
            List<PowerPoint.Shape> instructorShapes = new List<PowerPoint.Shape>();
            List<InkSheetModel> inksheets = new List<InkSheetModel>();

            foreach (PowerPoint.Shape shape in currentSlide.Shapes) {
                PrepShape(slideWidth, slideHeight, pptpm, shape, inksheets, shapeDisposition, instructorShapes);
            }

            string title = PPTDeckIO.FindSlideTitle(currentSlide, shapeDisposition);
            
            List<List<Effect>> effects = Effects(currentSlide.TimeLine.MainSequence);

            //Create a new SlideModel
            SlideModel newSlideModel = new SlideModel(Guid.NewGuid(), new LocalId(), SlideDisposition.Empty, new Rectangle(0, 0, slideWidth, slideHeight));

            //Lock it
            using (Synchronizer.Lock(newSlideModel.SyncRoot))
            {
                newSlideModel.Title = title;

                foreach (PowerPoint.Slide note in currentSlide.NotesPage)
                {
                    foreach (PowerPoint.Shape shape in note.Shapes)
                    {
                        try
                        {
                            if (shape.PlaceholderFormat.Type == PowerPoint.PpPlaceholderType.ppPlaceholderBody &&
                                shape.HasTextFrame == Core.MsoTriState.msoTrue && shape.TextFrame.HasText == Core.MsoTriState.msoTrue)
                                AddNotes(form, newSlideModel, shape.TextFrame.TextRange);
                        }
                        catch (Exception)
                        {
                        }
                    }
                }

                Debug.Print("New Slide: {0} layers,  {1} ink objects", effects.Count, inksheets.Count);
                newSlideModel.AnimationLayerCount = effects.Count - 1;

                foreach (InkSheetModel sheet in inksheets)
                {
                    newSlideModel.AnnotationSheets.Add(sheet);
                }

                for (int animLayer = 0; animLayer < effects.Count; animLayer++) {
                    List<Effect> step = effects[animLayer];
                    foreach (Effect e in step)
                        MakeVisible(e, e.appear);

                    List<PowerPoint.Shape> visibleInstructorShapes = new List<PowerPoint.Shape>();
                    foreach(PowerPoint.Shape shape in instructorShapes) {
                        if (shape.Visible == Core.MsoTriState.msoTrue) {
                            visibleInstructorShapes.Add(shape);
                            shape.Visible = Core.MsoTriState.msoFalse;
                        }
                    }

                    Debug.Print("  layer {0}, public sheet", animLayer);
                    ImageSheetModel sheet = ExportSlide(dirpath, tempFileCollection, slideWidth, slideHeight, deck, currentSlide, SheetDisposition.All, animLayer);
                    newSlideModel.ContentSheets.Add(sheet);

                    if (visibleInstructorShapes.Count > 0) {
                        foreach(PowerPoint.Shape shape in visibleInstructorShapes)
                            shape.Visible = Core.MsoTriState.msoTrue;
                        Debug.WriteLine(String.Format("  layer {0}, instructor sheet", animLayer));
                        ImageSheetModel isheet = ExportSlide(dirpath, tempFileCollection, slideWidth, slideHeight, deck, currentSlide, SheetDisposition.Instructor, animLayer);
                        newSlideModel.ContentSheets.Add(isheet);
                    }
                }


                //Add SlideModel to the deck
                deck.InsertSlide(newSlideModel);
            }
            return newSlideModel;
        }

        private static void PrepShape(int slideWidth, int slideHeight, PPTPaneManager pptpm, PowerPoint.Shape shape, List<InkSheetModel> inksheets, Dictionary<PowerPoint.Shape, SheetDisposition> shapeDisposition, List<PowerPoint.Shape> instructorShapes)
        {
            if (shape.Name.StartsWith("CP3 Ink "))
            {
                try
                {
                    Guid g = new Guid(shape.Name.Substring("CP3 Ink ".Length));
                    byte[] inkdata = pptpm.GetInk(shape);
                    if (inkdata != null)
                    {
                        Microsoft.Ink.Ink ink = new Microsoft.Ink.Ink();
                        ink.Load(inkdata);
                        InkSheetModel sheet = new InkSheetModel(g, SheetDisposition.All,
                            new Rectangle(0, 0, slideWidth, slideHeight), ink);
                        inksheets.Add(sheet);
                        shape.Delete();
                        return;
                    }
                }
                catch (Exception)
                {
                }
            }
            SheetDisposition disp = Disposition(pptpm.GetModes(shape));
            shapeDisposition.Add(shape, disp);
            if (disp != SheetDisposition.All || shape.Visible != Core.MsoTriState.msoTrue)
            {
                instructorShapes.Add(shape);
                shape.Visible = Core.MsoTriState.msoTrue;
            }
            if (shape.Type == Core.MsoShapeType.msoGroup)
            {
                foreach (PowerPoint.Shape s in shape.GroupItems)
                    PrepShape(slideWidth, slideHeight, pptpm, s, inksheets, shapeDisposition, instructorShapes);
            }
        }

        public delegate string GetClipboardTextDelegate();

        public static string GetClipboardText(Form form)
        {
            try
            {
                if (form != null && form.InvokeRequired)
                    return (string)form.Invoke(new GetClipboardTextDelegate(DoGetClipboardText));
                else
                    return DoGetClipboardText();
            } catch (Exception) {
                return "";
            }
        }
            
        private static string DoGetClipboardText() {
            return Clipboard.GetText(TextDataFormat.Rtf);
        }

        private static void AddNotes(Form form, SlideModel newSlideModel, PowerPoint.TextRange textRange)
        {
            // convert textrange to rtf via clipboard
            // this actually gets *more* formatting than visible in powerpoint's default note view
            textRange.Copy();
            string rtf = GetClipboardText(form);
            if (rtf == null || rtf.Equals(""))
                return;
            // it seems to lose the some bullet off the left margin, b/c powerpoint inserts "\\fi-540" (or "\\fi-1000") for a hanging indent paragraph
            // if we replace those indents, at least the 540 ones, with zero, we get the bullets back
            rtf = rtf.Replace("\\fi-540", "\\fi0");
            if (newSlideModel.Notes == null)
                newSlideModel.Notes = rtf;
        }

        /// <summary>
        /// Find the title of the slide.  
        /// </summary>
        /// <param name="tsList"></param>
        /// <returns></returns>
        private static string FindSlideTitle(PowerPoint._Slide slide, Dictionary<PowerPoint.Shape, SheetDisposition> shapeDisposition) {
            try {
                //First look for a proper title on the slide.  Assume a slide title always has SheetDisposition.All.
                if (slide.Shapes.HasTitle == Microsoft.Office.Core.MsoTriState.msoTrue &&
                    slide.Shapes.Title.HasTextFrame == Microsoft.Office.Core.MsoTriState.msoTrue &&
                    slide.Shapes.Title.TextFrame.HasText == Microsoft.Office.Core.MsoTriState.msoTrue) {
                    return PPTDeckIO.CleanString(slide.Shapes.Title.TextFrame.TextRange.Text);
                }

                //If there is no title, take the first object with text that has SheetDispositon.All
                foreach (PowerPoint.Shape shape in slide.Shapes) {
                    if (shapeDisposition[shape] == SheetDisposition.All &&
                        shape.HasTextFrame == Microsoft.Office.Core.MsoTriState.msoTrue &&
                        shape.TextFrame.HasText == Microsoft.Office.Core.MsoTriState.msoTrue) {
                        return PPTDeckIO.CleanString(shape.TextFrame.TextRange.Text);
                    }
                }
            }
            catch (Exception e) {
                Debug.WriteLine(e.ToString());
            }

            //Last resort: Set the title to "Slide N"
            return "Slide " + slide.SlideIndex.ToString();
        }

        #endregion

        #region OpenCP3 Method

        public static DeckModel OpenCP3(FileInfo file) {
            //Deserialize it
            FileStream fs = file.Open(FileMode.Open, FileAccess.Read);

            try {
                BinaryFormatter bf = new BinaryFormatter();
                DeckModel toReturn = (DeckModel)bf.Deserialize(fs);
                toReturn.Filename = file.FullName;
                return toReturn;
            } finally {
                fs.Close();
            }
        }

        #endregion

        #region SaveAsCP3 Method

        public static bool SaveAs(FileInfo file, DeckModel in_deck, bool merge) {
            //Update the deck's filename
            DeckModel deck = in_deck.Copy();
            deck.current_subs = false;
            deck.current_poll = false;
            if ((deck.Disposition & DeckDisposition.Remote) != 0){
                deck.Disposition = DeckDisposition.Empty;
            }
            using (Synchronizer.Lock(deck)) {
                deck.Filename = file.FullName;
            }

            if (file.Name.EndsWith(".ppt") || file.Name.EndsWith(".pptx"))
            {
                Misc.ProgressBarForm pbf = new Misc.ProgressBarForm("Saving \"" + file.Name + "\"...");
                pbf.DoWork += new DoWorkEventHandler((Object sender, DoWorkEventArgs progress) =>
                {
                    try
                    {
                        PPTDeckIO.SaveAsPPT(file, deck, merge, (Misc.ProgressBarForm)sender, progress);
                    }
                    catch (Exception e)
                    {
                        progress.Result = e;
                        progress.Cancel = true;
                    }
                } );
                pbf.Show();
                return pbf.StartWork(null, false);
            }

            //Serialize it
            FileStream fs = file.Open(FileMode.Create, FileAccess.Write);
            try {
                BinaryFormatter bf = new BinaryFormatter();
                bf.Serialize(fs, deck);
                fs.Flush();
                return true;
            } catch (Exception) {
                return false;
            } finally {
                fs.Close();
            }
        }

        #endregion

        private static void SaveAsPPT(FileInfo file, DeckModel deck, bool merge, Misc.ProgressBarForm worker, DoWorkEventArgs progress)
        {
            if (worker != null)
                worker.ReportProgress(0, "  Initializing...");

            bool pptAlreadyRunning = false;
            Process[] processes = Process.GetProcesses();
            for (int i = 0; i < processes.Length; i++) {
                string currentProcess = processes[i].ProcessName.ToLower();
                if (currentProcess == "powerpnt") {
                    pptAlreadyRunning = true;
                    break;
                }
            }

            PowerPoint.Application pptapp;
            try {
                pptapp = new PowerPoint.Application();
            }
            catch (Exception e) {
                throw new PPTNotInstalledException("Failed to create PowerPoint Application.  See InnerException for details.", e);
            }

            if (!file.Exists)
                merge = false;

            PowerPoint._Presentation presentation;
            try
            {
                if (merge)
                    presentation = pptapp.Presentations.Open(file.FullName, Core.MsoTriState.msoFalse, Core.MsoTriState.msoFalse, Core.MsoTriState.msoFalse);
                else
                    presentation = pptapp.Presentations.Add(Core.MsoTriState.msoFalse);
            }
            catch (Exception e)
            {
                throw new PPTFileOpenException("Failed to open or create PowerPoint file.  See InnerException for details.", e);
            }

            //Initialize the PPT Shape tag reader/writer
            PPTPaneManagement.PPTPaneManager pptpm = new PPTPaneManagement.PPTPaneManager();


            //Initialize a temporary file collection that will be where slide images are exported to
            TempFileCollection tempFileCollection = new TempFileCollection();
            string dirpath = tempFileCollection.BasePath;
            if (!Directory.Exists(dirpath))
            {
                Directory.CreateDirectory(dirpath);
            }
            else
            {
                Directory.Delete(dirpath, true);
                Directory.CreateDirectory(dirpath);
            }

            if (merge)
            {
                PowerPoint.CustomLayout layout;
                if (presentation.Slides.Count > 0)
                    layout = presentation.Slides[presentation.Slides.Count].CustomLayout;
                else
                    layout = presentation.SlideMaster.CustomLayouts[0];

                // export each inksheet, insert into slide	
	            int i = 0;
                using (Synchronizer.Lock(deck.SyncRoot))
                {
                    for (int tocIdx = 0; tocIdx < deck.TableOfContents.Entries.Count; tocIdx++) {
                        TableOfContentsModel.Entry entry = deck.TableOfContents.Entries[tocIdx];
                        SlideModel slide = entry.Slide;
                        if (progress.Cancel)
                            break;
                        i++;
                        if (worker != null)
                            worker.ReportProgress((i * 100) / (deck.Slides.Count + 1), "  Saving ink for slide " + i + " of " + deck.Slides.Count);
                        while (i > presentation.Slides.Count)
                            presentation.Slides.AddSlide(presentation.Slides.Count + 1, layout);
                        PowerPoint._Slide currentSlide = presentation.Slides[i];
                        if (tocIdx + 1 < deck.TableOfContents.Entries.Count && slide.Origin == deck.TableOfContents.Entries[tocIdx + 1].Slide.Origin)
                        {
                            // next is a dup of current slide, so duplicate it
                            currentSlide.Duplicate();
                        }
                        using (Synchronizer.Lock(slide.SyncRoot))
                        {
                            foreach (SheetModel sheet in slide.AnnotationSheets)
                            {
                                if (progress.Cancel)
                                    break;
                                if (sheet is InkSheetModel)
                                {
                                    using (Synchronizer.Lock(sheet.SyncRoot))
                                    {
                                        Microsoft.Ink.Ink ink = ((InkSheetModel)sheet).Ink;
                                        // see if already present
                                        try
                                        {
                                            PowerPoint.ShapeRange prev = currentSlide.Shapes.Range("CP3 Ink " + sheet.Id.ToString());
                                            if (prev.Count > 0)
                                                prev.Delete();
                                        }
                                        catch (Exception)
                                        {
                                        }
                                        if (ink.Strokes.Count != 0)
                                        {
                                            try
                                            {
                                                byte[] inkdata = ink.Save(Microsoft.Ink.PersistenceFormat.Base64InkSerializedFormat);
                                                ink.ClipboardCopy(Microsoft.Ink.InkClipboardFormats.EnhancedMetafile, Microsoft.Ink.InkClipboardModes.Copy);
                                                PowerPoint.ShapeRange shapes = currentSlide.Shapes.Paste();
                                                Rectangle r = ink.GetBoundingBox();
                                                float cp3ToPpt = 96f / 72; // scales CP3 ink resolution (dpi = 96) to ppt resolution (dpi = 72)
                                                shapes.ScaleWidth(cp3ToPpt, Core.MsoTriState.msoTrue, Core.MsoScaleFrom.msoScaleFromTopLeft);
                                                shapes.ScaleHeight(cp3ToPpt, Core.MsoTriState.msoTrue, Core.MsoScaleFrom.msoScaleFromTopLeft);
                                                float inkToPt = 96 / 2.54f / 1000; // scales ink (himetric = 1/1000 cm) to pixels (dpi = 1/96 in)
                                                // BoundingBox doesn't quite match pasted size, so we align center instead of top left corner
                                                shapes.Left = (r.X + r.Width/2) * inkToPt - shapes.Width / 2;
                                                shapes.Top = (r.Y + r.Height / 2) * inkToPt - shapes.Height / 2;
                                                shapes.Name = "CP3 Ink " + ((InkSheetModel)sheet).Id.ToString();
                                                pptpm.SetInk(shapes, inkdata);
                                            }
                                            catch (Exception err)
                                            {
                                                Debug.Print(err.Message);
                                                progress.Cancel = true;
                                                progress.Result = err;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            
            if (!progress.Cancel) {
                try
                {
                    if (merge)
                        presentation.Save();
                    else
                        presentation.SaveAs(file.FullName);
                }
                catch (Exception err)
                {
                    Debug.Print(err.Message);
                    progress.Cancel = true;
                    progress.Result = new PPTFileSaveException("Failed to save PowerPoint file.  See InnerException for details.", err);
                }
            }
            presentation.Close();
            presentation = null;
            //If PowerPoint was not open before, close PowerPoint
            if (!pptAlreadyRunning) {
                pptapp.Quit();
                pptapp = null;
            }
            GC.Collect();
            //Delete temp directory
            tempFileCollection.Delete();
            Directory.Delete(dirpath);

            if (worker != null)
                worker.ReportProgress(100, " Done!");
        }

        #region Export Deck Method

        //WARNING - This method assumes that all necessary sanity checks for path have been done!
        /// <summary>
        /// 
        /// </summary>
        /// <param name="traversal"></param>
        /// <param name="path"></param>
        /// <param name="format"></param>
        /// <returns>a List of strings with all the names of the image files</returns>
        public static List<string> ExportDeck(DefaultDeckTraversalModel traversal, string path, System.Drawing.Imaging.ImageFormat format) {
            return PPTDeckIO.ExportDeck(traversal, path, format, 0, 0, 1.0f);
        }

        //WARNING - This method assumes that all necessary sanity checks for path have been done!
        /// <summary>
        /// 
        /// </summary>
        /// <param name="traversal"></param>
        /// <param name="path"></param>
        /// <param name="format"></param>
        /// <returns>a List of strings with all the names of the image files</returns>
        public static List<string> ExportDeck(DefaultDeckTraversalModel traversal, string path, System.Drawing.Imaging.ImageFormat format, int width, int height, float scale ) {
            List<string> file_names = new List<string>();
            //Info here:  http://gotdotnet.com/Community/MessageBoard/Thread.aspx?id=27804
            //Iterate over all the slides
            using (Synchronizer.Lock(traversal.SyncRoot)) {
                using (Synchronizer.Lock(traversal.Deck.SyncRoot)) {
                    using (Synchronizer.Lock(traversal.Deck.TableOfContents.SyncRoot)) {
                        //Create the directory, if it doesn't already exist
                        if (!Directory.Exists(path)) {
                            Directory.CreateDirectory(path);
                        }

                        int imageCount = 1;
                        TableOfContentsModel.Entry currentEntry = traversal.Deck.TableOfContents.Entries[0];
                        while (currentEntry != null) {
                            Color background = Color.Transparent;
                            BackgroundTemplate template = null;

                            // Add the background color
                            // First see if there is a Slide BG, if not, try the Deck.  Otherwise, use transparent.
                            using (Synchronizer.Lock(currentEntry.Slide.SyncRoot)) {
                                if (currentEntry.Slide.BackgroundColor != Color.Empty) {
                                    background = currentEntry.Slide.BackgroundColor;
                                }
                                else if (traversal.Deck.DeckBackgroundColor != Color.Empty) {
                                    background = traversal.Deck.DeckBackgroundColor;
                                }
                                if (currentEntry.Slide.BackgroundTemplate != null) {
                                    template = currentEntry.Slide.BackgroundTemplate;
                                }
                                else if (traversal.Deck.DeckBackgroundTemplate != null) {
                                    template = traversal.Deck.DeckBackgroundTemplate;
                                }
                            }

                            // Get the Image
                            Bitmap toExport = DrawSlide(currentEntry, template, background, SheetDisposition.Background | SheetDisposition.Public | SheetDisposition.Student | SheetDisposition.All, width, height, scale );

                            // Format the imageCount
                            string number = "";
                            if (imageCount >= 0 && imageCount < 10) {
                                number = "00" + imageCount;
                            }
                            else if (imageCount >= 10 && imageCount < 100) {
                                number = "0" + imageCount;
                            }
                            else {
                                number = "" + imageCount;
                            }

                            // Get the extension string based on the exported image type
                            string extension = "";
                            if (format == System.Drawing.Imaging.ImageFormat.Bmp) {
                                extension = "bmp";
                            }
                            else if (format == System.Drawing.Imaging.ImageFormat.Emf) {
                                extension = "emf";
                            }
                            else if (format == System.Drawing.Imaging.ImageFormat.Exif) {
                                extension = "exf";
                            }
                            else if (format == System.Drawing.Imaging.ImageFormat.Gif) {
                                extension = "gif";
                            }
                            else if (format == System.Drawing.Imaging.ImageFormat.Icon) {
                                extension = "ico";
                            }
                            else if (format == System.Drawing.Imaging.ImageFormat.Jpeg) {
                                extension = "jpg";
                            }
                            else if (format == System.Drawing.Imaging.ImageFormat.Png) {
                                extension = "png";
                            }
                            else if (format == System.Drawing.Imaging.ImageFormat.Tiff) {
                                extension = "tif";
                            }
                            else if (format == System.Drawing.Imaging.ImageFormat.Wmf) {
                                extension = "wmf";
                            }
                            else {
                                extension = "bmp";
                            }

                            // Construct the complete file name and save
                            string file_name = traversal.Deck.HumanName + "_" + number + "." + extension;
                            toExport.Save(path + "\\" + file_name, format);

                            /// Save the file name to our list
                            file_names.Add(file_name);

                            // Increase the counter
                            imageCount++;

                            // Done, get next entry
                            currentEntry = traversal.FindNext(currentEntry);

                            // Cleanup
                            toExport.Dispose();
                        }
                    }
                }
            }
            return file_names;
        }

        // NOTE: Eventually this code should be converted to use SlideRenderer instead of each SheetRenderer. There were some issues with doing 
        // this initially so for the time being we will keep it like this.
        public static Bitmap DrawSlide(TableOfContentsModel.Entry currentEntry, BackgroundTemplate template, System.Drawing.Color background, SheetDisposition dispositions) {
            return PPTDeckIO.DrawSlide(currentEntry, template, background, dispositions, 0, 0, 1.0f);
        }

        // NOTE: Eventually this code should be converted to use SlideRenderer instead of each SheetRenderer. There were some issues with doing 
        // this initially so for the time being we will keep it like this.
        public static Bitmap DrawSlide(TableOfContentsModel.Entry currentEntry, BackgroundTemplate template, System.Drawing.Color background, SheetDisposition dispositions, int width, int height, float scale ) {
            // Save the old state
            System.Drawing.Drawing2D.GraphicsState oldState;

            using (Synchronizer.Lock(currentEntry.Slide.SyncRoot)) {
                Rectangle rect;
                int boundsWidth = currentEntry.Slide.Bounds.Width;
                int boundsHeight = currentEntry.Slide.Bounds.Height;
                int exportWidth = width;
                int exportHeight = height;

                if (width > 0 && height > 0) {
                    // Do Nothing
                }
                else if (width > 0) {
                    exportHeight = (int)Math.Round( ( (float)width / (float)boundsWidth ) * (float)boundsHeight );
                }
                else if (height > 0) {
                    exportWidth = (int)Math.Round( ( (float)height / (float)boundsHeight ) * (float)boundsWidth );
                }
                else {
                    exportWidth = boundsWidth;
                    exportHeight = boundsHeight;
                }

                // Scale the size
                exportWidth = (int)Math.Round( exportWidth * scale );
                exportHeight = (int)Math.Round( exportHeight * scale );
                rect = new Rectangle(0, 0, exportWidth, exportHeight);

                //Note: Uses DibGraphicsBuffer from TPC SDK to do antialiasing
                //See: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dntablet/html/tbconprintingink.asp
                /// create the bitmap we're exporting to
                Bitmap toExport = new Bitmap(rect.Width, rect.Height);
                /// create what we will be drawing on to the export
                Graphics toSave = Graphics.FromImage(toExport);

                /// draw the slide data on a temporary graphics object in a temporary form
                System.Windows.Forms.Form tempForm = new System.Windows.Forms.Form();
                Graphics screenGraphics = tempForm.CreateGraphics();
                DibGraphicsBuffer dib = new DibGraphicsBuffer();
                Graphics tempGraphics = dib.RequestBuffer(screenGraphics, rect.Width, rect.Height);

                //Add the background color
                //First see if there is a Slide BG, if not, try the Deck.  Otherwise, use transparent.
                tempGraphics.Clear(background);

                //Add the background template
                if (template != null)
                {
                    using (BackgroundTemplateRenderer bkgRender = new BackgroundTemplateRenderer(template))
                    {
                        bkgRender.DrawAll(tempGraphics, rect);
                    }
                }

                //Get the Slide content and draw it
                oldState = tempGraphics.Save();

                Model.Presentation.SlideModel.SheetCollection sheets = currentEntry.Slide.ContentSheets;
                for (int i = 0; i < sheets.Count && i < 3; i++)
                {
                    Model.Viewer.SlideDisplayModel display = new Model.Viewer.SlideDisplayModel(tempGraphics, null, null);

                    Rectangle slide = rect;
                    float zoom = 1f;
                    if (currentEntry.Slide != null)
                    {
                        slide = currentEntry.Slide.Bounds;
                        zoom = currentEntry.Slide.Zoom;
                    }

                    System.Drawing.Drawing2D.Matrix pixel, ink;
                    display.FitSlideToBounds(System.Windows.Forms.DockStyle.Fill, rect, zoom, ref slide, out pixel, out ink);
                    using (Synchronizer.Lock(display.SyncRoot))
                    {
                        display.SheetDisposition = dispositions;
                        display.Bounds = slide;
                        display.PixelTransform = pixel;
                        display.InkTransform = ink;
                    }

                    Viewer.Slides.SheetRenderer r = Viewer.Slides.SheetRenderer.ForStaticSheet(display, sheets[i]);
                    if ((r.Sheet.Disposition & dispositions) != 0)
                        r.Paint(new System.Windows.Forms.PaintEventArgs(tempGraphics, rect));
                    r.Dispose();
                }

                //Restore the Old State
                tempGraphics.Restore(oldState);
                oldState = tempGraphics.Save();

                //Get the Annotation content and draw it
                sheets = currentEntry.Slide.AnnotationSheets;
                for (int i = 0; i < sheets.Count; i++)
                {
                    Model.Viewer.SlideDisplayModel display = new Model.Viewer.SlideDisplayModel(tempGraphics, null, null);

                    Rectangle slide = rect;
                    float zoom = 1f;
                    if (currentEntry.Slide != null)
                    {
                        slide = currentEntry.Slide.Bounds;
                        zoom = currentEntry.Slide.Zoom;
                    }

                    System.Drawing.Drawing2D.Matrix pixel, ink;
                    display.FitSlideToBounds(System.Windows.Forms.DockStyle.Fill, rect, zoom, ref slide, out pixel, out ink);
                    using (Synchronizer.Lock(display.SyncRoot))
                    {
                        display.SheetDisposition = dispositions;
                        display.Bounds = slide;
                        display.PixelTransform = pixel;
                        display.InkTransform = ink;
                    }

                    Viewer.Slides.SheetRenderer r = Viewer.Slides.SheetRenderer.ForStaticSheet(display, sheets[i]);
                    if ((r.Sheet.Disposition & dispositions) != 0)
                        r.Paint(new System.Windows.Forms.PaintEventArgs(tempGraphics, rect));
                    r.Dispose();
                }

                //Restore the Old State
                tempGraphics.Restore(oldState);

                //Export the image
                //Merge the graphics
                dib.PaintBuffer(toSave, 0, 0);

                //Dispose all the graphics
                toSave.Dispose();
                screenGraphics.Dispose();
                tempGraphics.Dispose();

                return toExport;
            }
        }

        #endregion

        #region Helper Methods

        /// <summary>
        /// Make sure all characters in a string are printable - replace any out of range characters by a space
        /// </summary>
        /// <param name="str">Input string</param>
        /// <returns></returns>
        private static string CleanString(string str){
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < str.Length; i++){
                char c = str[i];
                if (! (Char.IsLetterOrDigit(c) || Char.IsPunctuation(c) || Char.IsSymbol(c))) {
                    sb.Append(' ');
                } else {
                    sb.Append(c);
                }
            }
            return sb.ToString();
        }

        /// <summary>
        /// Construct a new file name, which is unique for the session
        /// </summary>
        /// <returns>File name of the from "cptmpfilexx"</returns>
        private static string GenerateFilename(string dirpath, string ext) {
            string result;
            do {
                result = dirpath + "\\" + "cptmpfile" + tmpFileCtr + ext;
                tmpFileCtr++;
            } while (File.Exists(result));
            return result;
        }

        private static int tmpFileCtr = 0;

        /// <summary>
        /// Convert from a string to the corresponding sheet disposition.  
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        private static Model.Presentation.SheetDisposition Disposition(string str) {
            // note: ppt plugin only supports Instructor and default
            if (str.Equals("Instructor"))
                return Model.Presentation.SheetDisposition.Instructor;
            if (str.Equals("Student"))
                return Model.Presentation.SheetDisposition.Student;
            if (str.Equals("Shared"))
                return Model.Presentation.SheetDisposition.Public;

            return Model.Presentation.SheetDisposition.All;             // All as the default
        }

        private static Model.Presentation.SheetDisposition Disposition(string[] modes)
        {
            Debug.Assert(modes.Length <= 1);
            if (modes.Length == 0)
                return Model.Presentation.SheetDisposition.All;
            else
                return Disposition(modes[0]);
        }

        #endregion
    }
}
