package presentation

import (
	"archive/zip"
	"bytes"
	"encoding/xml"
	"errors"
	"flag"
	"fmt"
	"image"
	"image/jpeg"
	"io"
	"log"
	"os"
	"path"

	"go.devnw.com/ooxml"
	"go.devnw.com/ooxml/color"
	"go.devnw.com/ooxml/common"
	"go.devnw.com/ooxml/common/license"
	"go.devnw.com/ooxml/measurement"
	"go.devnw.com/ooxml/schema/soo/dml"
	crt "go.devnw.com/ooxml/schema/soo/dml/chart"
	"go.devnw.com/ooxml/schema/soo/ofc/sharedTypes"
	"go.devnw.com/ooxml/schema/soo/pkg/relationships"
	"go.devnw.com/ooxml/schema/soo/pml"
	"go.devnw.com/ooxml/zippkg"
)

// Presentation is the a presentation base document.
type Presentation struct {
	common.DocBase
	x                      *pml.Presentation
	prels                  common.Relationships
	slides                 []*pml.Sld
	slideRels              []common.Relationships
	masters                []*pml.SldMaster
	masterRels             []common.Relationships
	layouts                []*pml.SldLayout
	layoutRels             []common.Relationships
	themes                 []*dml.Theme
	themeRels              []common.Relationships
	tableStyles            common.TableStyles
	presentationProperties PresentationProperties
	viewProperties         ViewProperties
	hyperlinks             []*dml.CT_Hyperlink
	charts                 []*crt.ChartSpace
	handoutMaster          []*pml.HandoutMaster
	notesMaster            []*pml.NotesMaster
	customXML              []*office.XSDAny
	imagesMap              map[string]string // mapping input images paths to output ones
}

func newEmpty() *Presentation {
	p := &Presentation{x: pml.NewPresentation()}
	p.x.SldIdLst = pml.NewCT_SlideIdList()
	p.x.ConformanceAttr = sharedTypes.ST_ConformanceClassTransitional
	p.AppProperties = common.NewAppProperties()
	p.CoreProperties = common.NewCoreProperties()
	p.tableStyles = common.NewTableStyles()
	p.ContentTypes = common.NewContentTypes()
	p.Rels = common.NewRelationships()
	p.prels = common.NewRelationships()
	p.presentationProperties = NewPresentationProperties()
	p.viewProperties = NewViewProperties()
	p.imagesMap = map[string]string{}
	return p
}

// New initializes and reurns a new presentation
func New() *Presentation {
	p := newEmpty()

	p.ContentTypes.AddOverride("/ppt/presentation.xml", "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml")

	p.Rels.AddRelationship("docProps/core.xml", "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties")
	p.Rels.AddRelationship("docProps/app.xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties")
	p.Rels.AddRelationship("ppt/presentation.xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument")
	p.Rels.AddRelationship("ppt/presProps.xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProps")
	p.Rels.AddRelationship("ppt/viewProps.xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/viewProps")
	p.Rels.AddRelationship("ppt/tableStyles.xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles")

	p.x.SldMasterIdLst = pml.NewCT_SlideMasterIdList()
	m := pml.NewSldMaster()
	m.ClrMap.Bg1Attr = dml.ST_ColorSchemeIndexLt1
	m.ClrMap.Bg2Attr = dml.ST_ColorSchemeIndexLt2
	m.ClrMap.Tx1Attr = dml.ST_ColorSchemeIndexDk1
	m.ClrMap.Tx2Attr = dml.ST_ColorSchemeIndexDk2
	m.ClrMap.Accent1Attr = dml.ST_ColorSchemeIndexAccent1
	m.ClrMap.Accent2Attr = dml.ST_ColorSchemeIndexAccent2
	m.ClrMap.Accent3Attr = dml.ST_ColorSchemeIndexAccent3
	m.ClrMap.Accent4Attr = dml.ST_ColorSchemeIndexAccent4
	m.ClrMap.Accent5Attr = dml.ST_ColorSchemeIndexAccent5
	m.ClrMap.Accent6Attr = dml.ST_ColorSchemeIndexAccent6
	m.ClrMap.HlinkAttr = dml.ST_ColorSchemeIndexHlink
	m.ClrMap.FolHlinkAttr = dml.ST_ColorSchemeIndexFolHlink

	p.masters = append(p.masters, m)

	smFn := office.AbsoluteFilename(office.DocTypePresentation, office.SlideMasterType, 1)
	p.ContentTypes.AddOverride(smFn, office.SlideMasterContentType)

	mrelID := p.prels.AddAutoRelationship(office.DocTypePresentation, office.OfficeDocumentType,
		1, office.SlideMasterType)
	smid := pml.NewCT_SlideMasterIdListEntry()
	smid.IdAttr = office.Uint32(2147483648)
	smid.RIdAttr = mrelID.ID()
	p.x.SldMasterIdLst.SldMasterId = append(p.x.SldMasterIdLst.SldMasterId, smid)
	mrel := common.NewRelationships()
	p.masterRels = append(p.masterRels, mrel)

	ls := pml.NewSldLayout()
	lrid := mrel.AddAutoRelationship(office.DocTypePresentation, office.SlideMasterType, 1, office.SlideLayoutType)
	slfn := office.AbsoluteFilename(office.DocTypePresentation, office.SlideLayoutType, 1)
	p.ContentTypes.AddOverride(slfn, office.SlideLayoutContentType)
	mrel.AddAutoRelationship(office.DocTypePresentation, office.SlideMasterType, 1, office.ThemeType)
	p.layouts = append(p.layouts, ls)

	m.SldLayoutIdLst = pml.NewCT_SlideLayoutIdList()
	lid := pml.NewCT_SlideLayoutIdListEntry()
	lid.IdAttr = office.Uint32(2147483649)
	lid.RIdAttr = lrid.ID()
	m.SldLayoutIdLst.SldLayoutId = append(m.SldLayoutIdLst.SldLayoutId, lid)

	lrel := common.NewRelationships()
	p.layoutRels = append(p.layoutRels, lrel)
	lrel.AddAutoRelationship(office.DocTypePresentation, office.SlideType, 1, office.SlideMasterType)
	p.x.NotesSz.CxAttr = 6858000
	p.x.NotesSz.CyAttr = 9144000

	thm := dml.NewTheme()

	thm.NameAttr = office.String("gooxml Theme")
	thm.ThemeElements.ClrScheme.NameAttr = "Office"
	thm.ThemeElements.ClrScheme.Dk1.SysClr = dml.NewCT_SystemColor()
	thm.ThemeElements.ClrScheme.Dk1.SysClr.LastClrAttr = office.String("000000")
	thm.ThemeElements.ClrScheme.Dk1.SysClr.ValAttr = dml.ST_SystemColorValWindowText

	thm.ThemeElements.ClrScheme.Lt1.SysClr = dml.NewCT_SystemColor()
	thm.ThemeElements.ClrScheme.Lt1.SysClr.LastClrAttr = office.String("ffffff")
	thm.ThemeElements.ClrScheme.Lt1.SysClr.ValAttr = dml.ST_SystemColorValWindow

	thm.ThemeElements.ClrScheme.Dk2.SrgbClr = dml.NewCT_SRgbColor()
	thm.ThemeElements.ClrScheme.Dk2.SrgbClr.ValAttr = "44546a"

	thm.ThemeElements.ClrScheme.Lt2.SrgbClr = dml.NewCT_SRgbColor()
	thm.ThemeElements.ClrScheme.Lt2.SrgbClr.ValAttr = "e7e7e6"

	thm.ThemeElements.ClrScheme.Accent1.SrgbClr = dml.NewCT_SRgbColor()
	thm.ThemeElements.ClrScheme.Accent1.SrgbClr.ValAttr = "4472c4"

	thm.ThemeElements.ClrScheme.Accent2.SrgbClr = dml.NewCT_SRgbColor()
	thm.ThemeElements.ClrScheme.Accent2.SrgbClr.ValAttr = "ed7d31"

	thm.ThemeElements.ClrScheme.Accent3.SrgbClr = dml.NewCT_SRgbColor()
	thm.ThemeElements.ClrScheme.Accent3.SrgbClr.ValAttr = "a5a5a5"

	thm.ThemeElements.ClrScheme.Accent4.SrgbClr = dml.NewCT_SRgbColor()
	thm.ThemeElements.ClrScheme.Accent4.SrgbClr.ValAttr = "ffc000"

	thm.ThemeElements.ClrScheme.Accent5.SrgbClr = dml.NewCT_SRgbColor()
	thm.ThemeElements.ClrScheme.Accent5.SrgbClr.ValAttr = "5b9bd5"

	thm.ThemeElements.ClrScheme.Accent6.SrgbClr = dml.NewCT_SRgbColor()
	thm.ThemeElements.ClrScheme.Accent6.SrgbClr.ValAttr = "70ad47"

	thm.ThemeElements.ClrScheme.Hlink.SrgbClr = dml.NewCT_SRgbColor()
	thm.ThemeElements.ClrScheme.Hlink.SrgbClr.ValAttr = "0563c1"

	thm.ThemeElements.ClrScheme.FolHlink.SrgbClr = dml.NewCT_SRgbColor()
	thm.ThemeElements.ClrScheme.FolHlink.SrgbClr.ValAttr = "954f72"

	thm.ThemeElements.FontScheme.NameAttr = "Office"
	thm.ThemeElements.FontScheme.MajorFont.Latin.TypefaceAttr = "Calibri Light"
	thm.ThemeElements.FontScheme.MinorFont.Latin.TypefaceAttr = "Calibri"

	thm.ThemeElements.FmtScheme.NameAttr = office.String("Office")
	// fills
	fp := dml.NewEG_FillProperties()
	thm.ThemeElements.FmtScheme.FillStyleLst.EG_FillProperties = append(thm.ThemeElements.FmtScheme.FillStyleLst.EG_FillProperties, fp)
	fp.SolidFill = &dml.CT_SolidColorFillProperties{
		SchemeClr: &dml.CT_SchemeColor{ValAttr: dml.ST_SchemeColorValPhClr},
	}

	// rot fill 0
	fp = dml.NewEG_FillProperties()
	thm.ThemeElements.FmtScheme.FillStyleLst.EG_FillProperties = append(thm.ThemeElements.FmtScheme.FillStyleLst.EG_FillProperties, fp)
	// add it twice so OSX word doesn't choke
	thm.ThemeElements.FmtScheme.FillStyleLst.EG_FillProperties = append(thm.ThemeElements.FmtScheme.FillStyleLst.EG_FillProperties, fp)
	fp.GradFill = &dml.CT_GradientFillProperties{RotWithShapeAttr: office.Bool(true),
		GsLst: &dml.CT_GradientStopList{},
		Lin:   &dml.CT_LinearShadeProperties{}}
	fp.GradFill.Lin.AngAttr = office.Int32(5400000)
	fp.GradFill.Lin.ScaledAttr = office.Bool(false)

	gs := dml.NewCT_GradientStop()
	gs.PosAttr.ST_PositiveFixedPercentageDecimal = office.Int32(0)
	gs.SchemeClr = &dml.CT_SchemeColor{ValAttr: dml.ST_SchemeColorValPhClr}
	fp.GradFill.GsLst.Gs = append(fp.GradFill.GsLst.Gs, gs)

	gs = dml.NewCT_GradientStop()
	gs.PosAttr.ST_PositiveFixedPercentageDecimal = office.Int32(50000)
	gs.SchemeClr = &dml.CT_SchemeColor{ValAttr: dml.ST_SchemeColorValPhClr}
	fp.GradFill.GsLst.Gs = append(fp.GradFill.GsLst.Gs, gs)

	thm.ThemeElements.FmtScheme.LnStyleLst = dml.NewCT_LineStyleList()
	for i := 0; i < 3; i++ {
		lp := dml.NewCT_LineProperties()
		lp.WAttr = office.Int32(int32(6350 * (i + 1)))
		lp.CapAttr = dml.ST_LineCapFlat
		lp.CmpdAttr = dml.ST_CompoundLineSng
		lp.AlgnAttr = dml.ST_PenAlignmentCtr
		thm.ThemeElements.FmtScheme.LnStyleLst.Ln = append(thm.ThemeElements.FmtScheme.LnStyleLst.Ln, lp)
	}

	thm.ThemeElements.FmtScheme.EffectStyleLst = dml.NewCT_EffectStyleList()
	for i := 0; i < 3; i++ {
		ef := dml.NewCT_EffectStyleItem()
		ef.EffectLst = dml.NewCT_EffectList()
		thm.ThemeElements.FmtScheme.EffectStyleLst.EffectStyle = append(thm.ThemeElements.FmtScheme.EffectStyleLst.EffectStyle, ef)
	}

	sf := dml.NewEG_FillProperties()
	sf.SolidFill = &dml.CT_SolidColorFillProperties{
		SchemeClr: &dml.CT_SchemeColor{ValAttr: dml.ST_SchemeColorValPhClr},
	}
	thm.ThemeElements.FmtScheme.BgFillStyleLst.EG_FillProperties = append(thm.ThemeElements.FmtScheme.BgFillStyleLst.EG_FillProperties,
		sf)
	thm.ThemeElements.FmtScheme.BgFillStyleLst.EG_FillProperties = append(thm.ThemeElements.FmtScheme.BgFillStyleLst.EG_FillProperties,
		sf)
	thm.ThemeElements.FmtScheme.BgFillStyleLst.EG_FillProperties = append(thm.ThemeElements.FmtScheme.BgFillStyleLst.EG_FillProperties,
		fp)

	p.themes = append(p.themes, thm)
	themeFn := office.AbsoluteFilename(office.DocTypePresentation, office.ThemeType, 1)
	p.ContentTypes.AddOverride(themeFn, office.ThemeContentType)
	p.prels.AddAutoRelationship(office.DocTypePresentation, office.OfficeDocumentType, 1, office.ThemeType)

	thmRel := common.NewRelationships()
	p.themeRels = append(p.themeRels, thmRel)

	return p
}

// X returns the inner wrapped XML type.
func (p *Presentation) X() *pml.Presentation {
	return p.x
}

func (p *Presentation) nextSlideID() uint32 {
	id := uint32(256)
	for _, s := range p.x.SldIdLst.SldId {
		if s.IdAttr >= id {
			id = s.IdAttr + 1
		}
	}
	return id
}

// AddSlide adds a new slide to the presentation.
func (p *Presentation) AddSlide() Slide {
	sd := pml.NewCT_SlideIdListEntry()
	sd.IdAttr = p.nextSlideID()
	p.x.SldIdLst.SldId = append(p.x.SldIdLst.SldId, sd)

	slide := pml.NewSld()
	slide.CSld.SpTree.NvGrpSpPr.CNvPr.IdAttr = 1
	slide.CSld.SpTree.GrpSpPr.Xfrm = dml.NewCT_GroupTransform2D()
	slide.CSld.SpTree.GrpSpPr.Xfrm.Off = dml.NewCT_Point2D()
	slide.CSld.SpTree.GrpSpPr.Xfrm.Off.XAttr.ST_CoordinateUnqualified = office.Int64(0)
	slide.CSld.SpTree.GrpSpPr.Xfrm.Off.YAttr.ST_CoordinateUnqualified = office.Int64(0)
	slide.CSld.SpTree.GrpSpPr.Xfrm.Ext = dml.NewCT_PositiveSize2D()
	slide.CSld.SpTree.GrpSpPr.Xfrm.Ext.CxAttr = int64(0 * measurement.Point)
	slide.CSld.SpTree.GrpSpPr.Xfrm.Ext.CyAttr = int64(0 * measurement.Point)
	slide.CSld.SpTree.GrpSpPr.Xfrm.ChOff = slide.CSld.SpTree.GrpSpPr.Xfrm.Off
	slide.CSld.SpTree.GrpSpPr.Xfrm.ChExt = slide.CSld.SpTree.GrpSpPr.Xfrm.Ext

	p.slides = append(p.slides, slide)
	srelID := p.prels.AddAutoRelationship(office.DocTypePresentation, office.OfficeDocumentType,
		len(p.slides), office.SlideType)
	sd.RIdAttr = srelID.ID()

	slidefn := office.AbsoluteFilename(office.DocTypePresentation, office.SlideType, len(p.slides))
	p.ContentTypes.AddOverride(slidefn, office.SlideContentType)

	srel := common.NewRelationships()
	p.slideRels = append(p.slideRels, srel)
	// TODO: make the slide layout configurable
	srel.AddAutoRelationship(office.DocTypePresentation, office.SlideType,
		len(p.layouts), office.SlideLayoutType)

	return Slide{sd, slide, p}
}

// AddSlideWithLayout adds a new slide with content copied from a layout.  Normally you should
// use AddDefaultSlideWithLayout as it will do some post processing similar to PowerPoint to
// clear place holder text, etc.
func (p *Presentation) AddSlideWithLayout(l SlideLayout) (Slide, error) {
	sd := pml.NewCT_SlideIdListEntry()
	sd.IdAttr = 256
	for _, id := range p.x.SldIdLst.SldId {
		if id.IdAttr >= sd.IdAttr {
			sd.IdAttr = id.IdAttr + 1
		}
	}
	p.x.SldIdLst.SldId = append(p.x.SldIdLst.SldId, sd)

	slide := pml.NewSld()

	buf := bytes.Buffer{}
	enc := xml.NewEncoder(&buf)
	start := xml.StartElement{Name: xml.Name{Local: "slide"}}
	start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://schemas.openxmlformats.org/presentationml/2006/main"})
	start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns:a"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/main"})
	start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns:p"}, Value: "http://schemas.openxmlformats.org/presentationml/2006/main"})
	start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns:r"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"})
	start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns:sh"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"})
	start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns:xml"}, Value: "http://www.w3.org/XML/1998/namespace"})

	if err := l.x.CSld.MarshalXML(enc, start); err != nil {
		return Slide{}, err
	}
	enc.Flush()

	dec := xml.NewDecoder(&buf)
	slide.CSld = pml.NewCT_CommonSlideData()
	if err := dec.Decode(slide.CSld); err != nil {
		return Slide{}, err
	}

	// clear layout name on the slide
	slide.CSld.NameAttr = nil
	//, chc := range slide.CSld.SpTree.Choice {
	slide.CSld.SpTree.Choice = removeChoicesWithPics(slide.CSld.SpTree.Choice)

	p.slides = append(p.slides, slide)

	srelID := p.prels.AddAutoRelationship(office.DocTypePresentation, office.OfficeDocumentType,
		len(p.slides), office.SlideType)
	sd.RIdAttr = srelID.ID()

	slidefn := office.AbsoluteFilename(office.DocTypePresentation, office.SlideType, len(p.slides))
	p.ContentTypes.AddOverride(slidefn, office.SlideContentType)

	srel := common.NewRelationships()
	p.slideRels = append(p.slideRels, srel)
	slrLen := len(p.slideRels) - 1
	for i, lout := range p.layouts {
		if lout == l.X() {
			lrels := p.layoutRels[i]
			for _, lrel := range lrels.X().Relationship {
				if lrel.TypeAttr != office.SlideMasterType {
					p.slideRels[slrLen].X().Relationship = append(p.slideRels[slrLen].X().Relationship, lrel)
				}
			}
			srel.AddAutoRelationship(office.DocTypePresentation, office.SlideType,
				i+1, office.SlideLayoutType)
		}
	}
	csld := Slide{sd, slide, p}

	return csld, nil
}

func removeChoicesWithPics(choices []*pml.CT_GroupShapeChoice) []*pml.CT_GroupShapeChoice {
	var newChoices []*pml.CT_GroupShapeChoice
	for _, aChoice := range choices {
		if len(aChoice.Pic) == 0 {
			newChoices = append(newChoices, aChoice)
		}
	}
	return newChoices
}

// AddDefaultSlideWithLayout tries to replicate what PowerPoint does when
// inserting a slide with a new style by clearing placeholder content and removing
// some placeholders.  Use AddSlideWithLayout if you need more control.
func (p *Presentation) AddDefaultSlideWithLayout(l SlideLayout) (Slide, error) {
	sld, err := p.AddSlideWithLayout(l)

	for _, ph := range sld.PlaceHolders() {
		// clear all placeholder content
		ph.Clear()
		// and drop some of the placeholders (footer, slide date/time, slide number)
		switch ph.Type() {
		case pml.ST_PlaceholderTypeFtr, pml.ST_PlaceholderTypeDt, pml.ST_PlaceholderTypeSldNum:
			ph.Remove()
		}
	}

	return sld, err
}

// Save writes the presentation out to a writer in the Zip package format
func (p *Presentation) Save(w io.Writer) error {
	return p.save(w, false)
}

// SaveAsTemplate writes the presentation out to a writer in the Zip package format as a template
func (p *Presentation) SaveAsTemplate(w io.Writer) error {
	return p.save(w, true)
}

func (p *Presentation) save(w io.Writer, isTemplate bool) error {
	if err := p.x.Validate(); err != nil {
		log.Printf("validation error in document: %s", err)
	}

	if !license.GetLicenseKey().IsLicensed() && flag.Lookup("test.v") == nil {
		fmt.Println("Unlicensed version of UniOffice")
		fmt.Println("- Get a license on https://unidoc.io")
		slide := p.Slides()[0]
		tb := slide.AddTextBox()
		tb.Properties().SetPosition(0, 0)
		tb.Properties().LineProperties().SetWidth(2 * measurement.Inch)
		p := tb.AddParagraph()
		r := p.AddRun()
		r.SetText("Unlicensed version of UniOffice - Get a license on https://unidoc.io")
		r.Properties().SetSize(12 * measurement.Point)
		r.Properties().SetBold(true)
		r.Properties().SetSolidFill(color.Red)
	}

	if isTemplate {
		p.ContentTypes.RemoveOverride("application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml")
		p.ContentTypes.EnsureOverride("/ppt/presentation.xml", "application/vnd.openxmlformats-officedocument.presentationml.template.main+xml")
	} else {
		p.ContentTypes.RemoveOverride("application/vnd.openxmlformats-officedocument.presentationml.template.main+xml")
		p.ContentTypes.EnsureOverride("/ppt/presentation.xml", "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml")
	}

	dt := office.DocTypePresentation

	z := zip.NewWriter(w)
	defer z.Close()
	if err := zippkg.MarshalXML(z, office.BaseRelsFilename, p.Rels.X()); err != nil {
		return err
	}
	if err := zippkg.MarshalXMLByType(z, dt, office.ExtendedPropertiesType, p.AppProperties.X()); err != nil {
		return err
	}
	if err := zippkg.MarshalXMLByType(z, dt, office.CorePropertiesType, p.CoreProperties.X()); err != nil {
		return err
	}
	if err := zippkg.MarshalXMLByType(z, dt, office.PresentationPropertiesType, p.presentationProperties.X()); err != nil {
		return err
	}
	if err := zippkg.MarshalXMLByType(z, dt, office.ViewPropertiesType, p.viewProperties.X()); err != nil {
		return err
	}
	if err := zippkg.MarshalXMLByType(z, dt, office.TableStylesType, p.tableStyles.X()); err != nil {
		return err
	}
	if p.CustomProperties.X() != nil {
		if err := zippkg.MarshalXMLByType(z, dt, office.CustomPropertiesType, p.CustomProperties.X()); err != nil {
			return err
		}
	}
	if p.Thumbnail != nil {
		tn, err := z.Create("docProps/thumbnail.jpeg")
		if err != nil {
			return err
		}
		if err := jpeg.Encode(tn, p.Thumbnail, nil); err != nil {
			return err
		}
	}

	documentFn := office.AbsoluteFilename(dt, office.OfficeDocumentType, 0)
	if err := zippkg.MarshalXML(z, documentFn, p.x); err != nil {
		return err
	}
	if err := zippkg.MarshalXML(z, zippkg.RelationsPathFor(documentFn), p.prels.X()); err != nil {
		return err
	}

	for i, slide := range p.slides {
		spath := office.AbsoluteFilename(office.DocTypePresentation, office.SlideType, i+1)
		zippkg.MarshalXML(z, spath, slide)
		if !p.slideRels[i].IsEmpty() {
			rpath := zippkg.RelationsPathFor(spath)
			zippkg.MarshalXML(z, rpath, p.slideRels[i].X())
		}
	}
	for i, m := range p.masters {
		mpath := office.AbsoluteFilename(office.DocTypePresentation, office.SlideMasterType, i+1)
		zippkg.MarshalXML(z, mpath, m)
		if !p.masterRels[i].IsEmpty() {
			rpath := zippkg.RelationsPathFor(mpath)
			zippkg.MarshalXML(z, rpath, p.masterRels[i].X())
		}
	}
	for i, l := range p.layouts {
		mpath := office.AbsoluteFilename(office.DocTypePresentation, office.SlideLayoutType, i+1)
		zippkg.MarshalXML(z, mpath, l)
		if !p.layoutRels[i].IsEmpty() {
			rpath := zippkg.RelationsPathFor(mpath)
			zippkg.MarshalXML(z, rpath, p.layoutRels[i].X())
		}
	}
	for i, l := range p.themes {
		mpath := office.AbsoluteFilename(office.DocTypePresentation, office.ThemeType, i+1)
		zippkg.MarshalXML(z, mpath, l)
		if !p.themeRels[i].IsEmpty() {
			rpath := zippkg.RelationsPathFor(mpath)
			zippkg.MarshalXML(z, rpath, p.themeRels[i].X())
		}
	}
	for i, chart := range p.charts {
		fn := office.AbsoluteFilename(dt, office.ChartType, i+1)
		zippkg.MarshalXML(z, fn, chart)
	}
	for i, hm := range p.handoutMaster {
		fn := office.AbsoluteFilename(dt, office.HandoutMasterType, i+1)
		zippkg.MarshalXML(z, fn, hm)
	}
	for i, nm := range p.notesMaster {
		fn := office.AbsoluteFilename(dt, office.NotesMasterType, i+1)
		zippkg.MarshalXML(z, fn, nm)
	}
	for i, cx := range p.customXML {
		fn := office.AbsoluteFilename(dt, office.CustomXMLType, i+1)
		zippkg.MarshalXML(z, fn, cx)
	}

	for i, img := range p.Images {
		if err := common.AddImageToZip(z, img, i+1, office.DocTypePresentation); err != nil {
			return err
		}
	}

	p.ContentTypes.EnsureDefault("png", "image/png")
	p.ContentTypes.EnsureDefault("jpeg", "image/jpeg")
	p.ContentTypes.EnsureDefault("jpg", "image/jpeg")
	p.ContentTypes.EnsureDefault("wmf", "image/x-wmf")
	if err := zippkg.MarshalXML(z, office.ContentTypesFilename, p.ContentTypes.X()); err != nil {
		return err
	}
	if err := p.WriteExtraFiles(z); err != nil {
		return err
	}
	return nil
}

// SaveToFile writes the Presentation out to a file.
func (p *Presentation) SaveToFile(path string) error {
	return p.saveToFile(path, false)
}

// SaveToFileAsTemplate writes the Presentation out to a file as a template.
func (p *Presentation) SaveToFileAsTemplate(path string) error {
	return p.saveToFile(path, true)
}

func (p *Presentation) saveToFile(path string, isTemplate bool) error {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()
	return p.save(f, isTemplate)
}

func (p *Presentation) Validate() error {
	if err := p.x.Validate(); err != nil {
		return err
	}
	for i, s := range p.Slides() {
		if err := s.ValidateWithPath(fmt.Sprintf("Slide[%d]", i)); err != nil {
			return err
		}
	}

	for i, sm := range p.masters {
		if err := sm.ValidateWithPath(fmt.Sprintf("SlideMaster[%d]", i)); err != nil {
			return err
		}
	}
	for i, sl := range p.layouts {
		if err := sl.ValidateWithPath(fmt.Sprintf("SlideLayout[%d]", i)); err != nil {
			return err
		}
	}
	return nil
}

// SlideMasters returns the slide masters defined in the presentation.
func (p *Presentation) SlideMasters() []SlideMaster {
	ret := []SlideMaster{}
	for i, m := range p.masters {

		ret = append(ret, SlideMaster{p, p.masterRels[i], m})
	}
	return ret
}

// SlideLayouts returns the slide layouts defined in the presentation.
func (p *Presentation) SlideLayouts() []SlideLayout {
	ret := []SlideLayout{}
	for _, l := range p.layouts {
		ret = append(ret, SlideLayout{l})
	}
	return ret
}

func (p *Presentation) onNewRelationship(decMap *zippkg.DecodeMap, target, typ string, files []*zip.File, rel *relationships.Relationship, src zippkg.Target) error {
	dt := office.DocTypePresentation

	switch typ {
	case office.OfficeDocumentType:
		p.x = pml.NewPresentation()
		decMap.AddTarget(target, p.x, typ, 0)
		decMap.AddTarget(zippkg.RelationsPathFor(target), p.prels.X(), typ, 0)
		rel.TargetAttr = office.RelativeFilename(dt, src.Typ, typ, 0)

	case office.CorePropertiesType:
		decMap.AddTarget(target, p.CoreProperties.X(), typ, 0)
		rel.TargetAttr = office.RelativeFilename(dt, src.Typ, typ, 0)

	case office.CustomPropertiesType:
		decMap.AddTarget(target, p.CustomProperties.X(), typ, 0)
		rel.TargetAttr = office.RelativeFilename(dt, src.Typ, typ, 0)

	case office.PresentationPropertiesType:
		decMap.AddTarget(target, p.presentationProperties.X(), typ, 0)
		rel.TargetAttr = office.RelativeFilename(dt, src.Typ, typ, 0)

	case office.ViewPropertiesType:
		decMap.AddTarget(target, p.viewProperties.X(), typ, 0)
		rel.TargetAttr = office.RelativeFilename(dt, src.Typ, typ, 0)

	case office.TableStylesType:
		decMap.AddTarget(target, p.tableStyles.X(), typ, 0)
		rel.TargetAttr = office.RelativeFilename(dt, src.Typ, typ, 0)

	case office.HyperLinkType:
		hl := dml.NewCT_Hyperlink()
		idx := uint32(len(p.hyperlinks))
		decMap.AddTarget(target, hl, typ, idx)
		p.hyperlinks = append(p.hyperlinks, hl)

	case office.CustomXMLType:
		cx := &office.XSDAny{}
		idx := uint32(len(p.customXML))
		decMap.AddTarget(target, cx, typ, idx)
		p.customXML = append(p.customXML, cx)
		rel.TargetAttr = office.RelativeFilename(dt, src.Typ, typ, len(p.customXML))

	case office.ChartType:
		chart := crt.NewChartSpace()
		idx := uint32(len(p.charts))
		decMap.AddTarget(target, chart, typ, idx)
		p.charts = append(p.charts, chart)
		rel.TargetAttr = office.RelativeFilename(dt, src.Typ, typ, len(p.charts))

	case office.HandoutMasterType:
		nm := pml.NewHandoutMaster()
		idx := uint32(len(p.handoutMaster))
		decMap.AddTarget(target, nm, typ, idx)
		p.handoutMaster = append(p.handoutMaster, nm)
		rel.TargetAttr = office.RelativeFilename(dt, src.Typ, typ, len(p.handoutMaster))

	case office.NotesMasterType:
		nm := pml.NewNotesMaster()
		idx := uint32(len(p.notesMaster))
		decMap.AddTarget(target, nm, typ, idx)
		p.notesMaster = append(p.notesMaster, nm)
		rel.TargetAttr = office.RelativeFilename(dt, src.Typ, typ, len(p.notesMaster))

	case office.ExtendedPropertiesType:
		decMap.AddTarget(target, p.AppProperties.X(), typ, 0)
		rel.TargetAttr = office.RelativeFilename(dt, src.Typ, typ, 0)

	case office.SlideType:
		sld := pml.NewSld()
		p.slides = append(p.slides, sld)
		decMap.AddTarget(target, sld, typ, uint32(len(p.slides)))
		rel.TargetAttr = office.RelativeFilename(dt, src.Typ, typ, len(p.slides))

		slRel := common.NewRelationships()
		decMap.AddTarget(zippkg.RelationsPathFor(target), slRel.X(), typ, 0)
		p.slideRels = append(p.slideRels, slRel)

	case office.SlideMasterType:
		sm := pml.NewSldMaster()
		if !decMap.AddTarget(target, sm, typ, uint32(len(p.masters)+1)) {
			return nil
		}
		p.masters = append(p.masters, sm)
		rel.TargetAttr = office.RelativeFilename(dt, src.Typ, typ, len(p.masters))

		// look for master rels
		smRel := common.NewRelationships()
		decMap.AddTarget(zippkg.RelationsPathFor(target), smRel.X(), typ, 0)
		p.masterRels = append(p.masterRels, smRel)

	case office.SlideLayoutType:
		sl := pml.NewSldLayout()
		if !decMap.AddTarget(target, sl, typ, uint32(len(p.layouts)+1)) {
			return nil
		}
		p.layouts = append(p.layouts, sl)
		rel.TargetAttr = office.RelativeFilename(dt, src.Typ, typ, len(p.layouts))

		// look for layout rels
		slRel := common.NewRelationships()
		decMap.AddTarget(zippkg.RelationsPathFor(target), slRel.X(), typ, 0)
		p.layoutRels = append(p.layoutRels, slRel)

	case office.ThumbnailType:
		// read our thumbnail
		for i, f := range files {
			if f == nil {
				continue
			}
			if f.Name == target {
				rc, err := f.Open()
				if err != nil {
					return fmt.Errorf("error reading thumbnail: %s", err)
				}
				p.Thumbnail, _, err = image.Decode(rc)
				rc.Close()
				if err != nil {
					return fmt.Errorf("error decoding thumbnail: %s", err)
				}
				files[i] = nil
			}
		}

	case office.ThemeType:
		thm := dml.NewTheme()
		if !decMap.AddTarget(target, thm, typ, uint32(len(p.themes)+1)) {
			return nil
		}
		p.themes = append(p.themes, thm)
		rel.TargetAttr = office.RelativeFilename(dt, src.Typ, typ, len(p.themes))

		// look for theme rels
		thmRel := common.NewRelationships()
		decMap.AddTarget(zippkg.RelationsPathFor(target), thmRel.X(), typ, 0)
		p.themeRels = append(p.themeRels, thmRel)

	case office.ImageType:
		// we use path.Clean instead of filepath.Clean to ensure we
		// end up with forward separators
		target = path.Clean(target)
		if targetAttr, ok := p.imagesMap[target]; ok {
			rel.TargetAttr = targetAttr
			return nil
		}
		format := ""
		for i, f := range files {
			if f == nil {
				continue
			}
			if f.Name == target {
				path, err := zippkg.ExtractToDiskTmp(f, p.TmpPath)
				if err != nil {
					return err
				}
				img, err := common.ImageFromFile(path)
				if err != nil {
					return err
				}
				format = img.Format
				iref := common.MakeImageRef(img, &p.DocBase, p.prels)
				p.Images = append(p.Images, iref)
				files[i] = nil
				decMap.RecordIndex(target, len(p.Images))
				break
			}
		}
		idx := decMap.IndexFor(target)
		rel.TargetAttr = office.RelativeImageFilename(dt, src.Typ, typ, idx, format)
		p.imagesMap[target] = rel.TargetAttr

	default:
		office.Log("unsupported relationship type: %s tgt: %s", typ, target)
	}
	return nil
}

// Slides returns the slides in the presentation.
func (p *Presentation) Slides() []Slide {
	ret := []Slide{}
	for i, v := range p.slides {
		ret = append(ret, Slide{p.x.SldIdLst.SldId[i], v, p})
	}
	return ret
}

// RemoveSlide removes a slide from a presentation.
func (p *Presentation) RemoveSlide(s Slide) error {
	removed := false
	slideIdx := 0
	for i, v := range p.slides {
		if v == s.x {
			if p.x.SldIdLst.SldId[i] != s.sid {
				return errors.New("inconsistency in slides and ID list")
			}
			copy(p.slides[i:], p.slides[i+1:])
			p.slides = p.slides[0 : len(p.slides)-1]

			copy(p.slideRels[i:], p.slideRels[i+1:])
			p.slideRels = p.slideRels[0 : len(p.slideRels)-1]

			copy(p.x.SldIdLst.SldId[i:], p.x.SldIdLst.SldId[i+1:])
			p.x.SldIdLst.SldId = p.x.SldIdLst.SldId[0 : len(p.x.SldIdLst.SldId)-1]

			removed = true
			slideIdx = i
		}
	}

	if !removed {
		return errors.New("unable to find slide")
	}

	// remove it from content types
	fn := office.AbsoluteFilename(office.DocTypePresentation, office.SlideType, 0)
	return p.ContentTypes.RemoveOverrideByIndex(fn, slideIdx)
}

// GetLayoutByName retrieves a slide layout given a layout name.
func (p *Presentation) GetLayoutByName(name string) (SlideLayout, error) {
	for _, l := range p.layouts {
		if l.CSld.NameAttr != nil && name == *l.CSld.NameAttr {
			return SlideLayout{l}, nil
		}
	}
	return SlideLayout{}, errors.New("unable to find layout with that name")
}

// AddImage adds an image to the document package, returning a reference that
// can be used to add the image to a run and place it in the document contents.
func (p *Presentation) AddImage(i common.Image) (common.ImageRef, error) {
	r := common.MakeImageRef(i, &p.DocBase, p.prels)
	if i.Data == nil && i.Path == "" {
		return r, errors.New("image must have data or a path")
	}

	if i.Format == "" {
		return r, errors.New("image must have a valid format")
	}
	if i.Size.X == 0 || i.Size.Y == 0 {
		return r, errors.New("image must have a valid size")
	}

	p.Images = append(p.Images, r)
	p.ContentTypes.EnsureDefault("png", "image/png")
	p.ContentTypes.EnsureDefault("jpeg", "image/jpeg")
	p.ContentTypes.EnsureDefault("jpg", "image/jpeg")
	p.ContentTypes.EnsureDefault("wmf", "image/x-wmf")
	p.ContentTypes.EnsureDefault(i.Format, "image/"+i.Format)
	return r, nil
}

// GetImageByRelID returns an ImageRef with the associated relation ID in the
// document.
func (p *Presentation) GetImageByRelID(relID string) (common.ImageRef, bool) {
	for _, img := range p.Images {
		if img.RelID() == relID {
			return img, true
		}
	}
	return common.ImageRef{}, false
}

// GetOrCreateCustomProperties returns the custom properties of the document (and if they not exist yet, creating them first)
func (p *Presentation) GetOrCreateCustomProperties() common.CustomProperties {
	if p.CustomProperties.X() == nil {
		p.createCustomProperties()
	}
	return p.CustomProperties
}

func (p *Presentation) createCustomProperties() {
	p.CustomProperties = common.NewCustomProperties()
	p.addCustomRelationships()
}

func (p *Presentation) addCustomRelationships() {
	p.ContentTypes.AddOverride("/docProps/custom.xml", "application/vnd.openxmlformats-officedocument.custom-properties+xml")
	p.Rels.AddRelationship("docProps/custom.xml", office.CustomPropertiesType)
}
