/*
Copyright 2018 The pdfcpu Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package pdfcpu

import (
	"bufio"
	"bytes"
	"encoding/hex"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"github.com/pdfcpu/pdfcpu/pkg/log"
	"github.com/pkg/errors"
)

// Write generates a PDF file for the cross reference table contained in Context.
func Write(ctx *Context) (err error) {
	// Create a writer for dirname and filename if not already supplied.
	if ctx.Write.Writer == nil {

		fileName := filepath.Join(ctx.Write.DirName, ctx.Write.FileName)
		log.CLI.Printf("writing to %s\n", fileName)

		file, err := os.Create(fileName)
		if err != nil {
			return errors.Wrapf(err, "can't create %s\n%s", fileName, err)
		}

		ctx.Write.Writer = bufio.NewWriter(file)

		defer func() {

			// The underlying bufio.Writer has already been flushed.

			// Processing error takes precedence.
			if err != nil {
				file.Close()
				return
			}

			// Do not miss out on closing errors.
			err = file.Close()

		}()

	}

	if err = prepareContextForWriting(ctx); err != nil {
		return err
	}

	// Since we support PDF Collections (since V1.7) for file attachments
	// we need to always generate V1.7 PDF files.
	if err = writeHeader(ctx.Write, V17); err != nil {
		return err
	}

	// Ensure there is no root version.
	if ctx.RootVersion != nil {
		ctx.RootDict.Delete("Version")
	}

	log.Write.Printf("offset after writeHeader: %d\n", ctx.Write.Offset)

	// Write root object(aka the document catalog) and page tree.
	if err = writeRootObject(ctx); err != nil {
		return err
	}

	log.Write.Printf("offset after writeRootObject: %d\n", ctx.Write.Offset)

	// Write document information dictionary.
	if err = ctx.writeDocumentInfoDict(); err != nil {
		return err
	}

	log.Write.Printf("offset after writeInfoObject: %d\n", ctx.Write.Offset)

	// Write offspec additional streams as declared in pdf trailer.
	if err = writeAdditionalStreams(ctx); err != nil {
		return err
	}

	if err = writeEncryptDict(ctx); err != nil {
		return err
	}

	// Mark redundant objects as free.
	// eg. duplicate resources, compressed objects, linearization dicts..
	deleteRedundantObjects(ctx)

	if err = writeXRef(ctx); err != nil {
		return err
	}

	// Write pdf trailer.
	if _, err = writeTrailer(ctx.Write); err != nil {
		return err
	}

	if err = setFileSizeOfWrittenFile(ctx.Write); err != nil {
		return err
	}

	if ctx.Read != nil {
		ctx.Write.BinaryImageSize = ctx.Read.BinaryImageSize
		ctx.Write.BinaryFontSize = ctx.Read.BinaryFontSize
		logWriteStats(ctx)
	}

	return nil
}

func prepareContextForWriting(ctx *Context) error {

	if err := ensureInfoDictAndFileID(ctx); err != nil {
		return err
	}

	return handleEncryption(ctx)
}

func writeAdditionalStreams(ctx *Context) error {

	if ctx.AdditionalStreams == nil {
		return nil
	}

	if _, _, err := writeDeepObject(ctx, ctx.AdditionalStreams); err != nil {
		return err
	}

	return nil
}

func ensureFileID(ctx *Context) error {

	fid, err := fileID(ctx)
	if err != nil {
		return err
	}

	if ctx.ID == nil {
		// Ensure ctx.ID
		ctx.ID = Array{fid, fid}
		return nil
	}

	// Update ctx.ID
	a := ctx.ID
	if len(a) != 2 {
		return errors.New("pdfcpu: ID must be an array with 2 elements")
	}

	a[1] = fid

	return nil
}

func ensureInfoDictAndFileID(ctx *Context) error {

	if err := ctx.ensureInfoDict(); err != nil {
		return err
	}

	return ensureFileID(ctx)
}

// Write root entry to disk.
func writeRootEntry(ctx *Context, d Dict, dictName, entryName string, statsAttr int) error {

	o, err := writeEntry(ctx, d, dictName, entryName)
	if err != nil {
		return err
	}

	if o != nil {
		ctx.Stats.AddRootAttr(statsAttr)
	}

	return nil
}

// Write root entry to object stream.
func writeRootEntryToObjStream(ctx *Context, d Dict, dictName, entryName string, statsAttr int) error {

	ctx.Write.WriteToObjectStream = true

	if err := writeRootEntry(ctx, d, dictName, entryName, statsAttr); err != nil {
		return err
	}

	return stopObjectStream(ctx)
}

// Write page tree.
func writePages(ctx *Context, rootDict Dict) error {

	// Page tree root (the top "Pages" dict) must be indirect reference.
	ir := rootDict.IndirectRefEntry("Pages")
	if ir == nil {
		return errors.New("pdfcpu: writePages: missing indirect obj for pages dict")
	}

	// Embed all page tree objects into objects stream.
	ctx.Write.WriteToObjectStream = true

	// Write page tree.
	p := 0
	if _, _, err := writePagesDict(ctx, ir, &p); err != nil {
		return err
	}

	return stopObjectStream(ctx)
}

func writeRootObject(ctx *Context) error {

	// => 7.7.2 Document Catalog

	xRefTable := ctx.XRefTable
	catalog := *xRefTable.Root
	objNumber := int(catalog.ObjectNumber)
	genNumber := int(catalog.GenerationNumber)

	log.Write.Printf("*** writeRootObject: begin offset=%d *** %s\n", ctx.Write.Offset, catalog)

	// Ensure corresponding and accurate name tree object graphs.
	// if !ctx.ApplyReducedFeatureSet() {
	if err := ctx.BindNameTrees(); err != nil {
		return err
	}
	// }

	d, err := xRefTable.DereferenceDict(catalog)
	if err != nil {
		return err
	}

	if d == nil {
		return errors.Errorf("pdfcpu: writeRootObject: unable to dereference root dict")
	}

	dictName := "rootDict"

	// if ctx.ApplyReducedFeatureSet() {
	// 	log.Write.Println("writeRootObject - reducedFeatureSet:exclude complex entries.")
	// 	d.Delete("Names")
	// 	d.Delete("Dests")
	// 	d.Delete("Outlines")
	// 	d.Delete("OpenAction")
	// 	d.Delete("AcroForm")
	// 	d.Delete("StructTreeRoot")
	// 	d.Delete("OCProperties")
	// }

	if err = writeDictObject(ctx, objNumber, genNumber, d); err != nil {
		return err
	}

	log.Write.Printf("writeRootObject: %s\n", d)

	log.Write.Printf("writeRootObject: new offset after rootDict = %d\n", ctx.Write.Offset)

	if err = writeRootEntry(ctx, d, dictName, "Version", RootVersion); err != nil {
		return err
	}

	if err = writePages(ctx, d); err != nil {
		return err
	}

	for _, e := range []struct {
		entryName string
		statsAttr int
	}{
		{"Extensions", RootExtensions},
		{"PageLabels", RootPageLabels},
		{"Names", RootNames},
		{"Dests", RootDests},
		{"ViewerPreferences", RootViewerPrefs},
		{"PageLayout", RootPageLayout},
		{"PageMode", RootPageMode},
		{"Outlines", RootOutlines},
		{"Threads", RootThreads},
		{"OpenAction", RootOpenAction},
		{"AA", RootAA},
		{"URI", RootURI},
		{"AcroForm", RootAcroForm},
		{"Metadata", RootMetadata},
	} {
		if err = writeRootEntry(ctx, d, dictName, e.entryName, e.statsAttr); err != nil {
			return err
		}
	}

	if err = writeRootEntryToObjStream(ctx, d, dictName, "StructTreeRoot", RootStructTreeRoot); err != nil {
		return err
	}

	for _, e := range []struct {
		entryName string
		statsAttr int
	}{
		{"MarkInfo", RootMarkInfo},
		{"Lang", RootLang},
		{"SpiderInfo", RootSpiderInfo},
		{"OutputIntents", RootOutputIntents},
		{"PieceInfo", RootPieceInfo},
		{"OCProperties", RootOCProperties},
		{"Perms", RootPerms},
		{"Legal", RootLegal},
		{"Requirements", RootRequirements},
		{"Collection", RootCollection},
		{"NeedsRendering", RootNeedsRendering},
	} {
		if err = writeRootEntry(ctx, d, dictName, e.entryName, e.statsAttr); err != nil {
			return err
		}
	}

	log.Write.Printf("*** writeRootObject: end offset=%d ***\n", ctx.Write.Offset)

	return nil
}

func writeTrailerDict(ctx *Context) error {

	log.Write.Printf("writeTrailerDict begin\n")

	w := ctx.Write
	xRefTable := ctx.XRefTable

	if _, err := w.WriteString("trailer"); err != nil {
		return err
	}

	if err := w.WriteEol(); err != nil {
		return err
	}

	d := NewDict()
	d.Insert("Size", Integer(*xRefTable.Size))
	d.Insert("Root", *xRefTable.Root)

	if xRefTable.Info != nil {
		d.Insert("Info", *xRefTable.Info)
	}

	if ctx.Encrypt != nil && ctx.EncKey != nil {
		d.Insert("Encrypt", *ctx.Encrypt)
	}

	if xRefTable.ID != nil {
		d.Insert("ID", xRefTable.ID)
	}

	if _, err := w.WriteString(d.PDFString()); err != nil {
		return err
	}

	log.Write.Printf("writeTrailerDict end\n")

	return nil
}

func writeXRefSubsection(ctx *Context, start int, size int) error {

	log.Write.Printf("writeXRefSubsection: start=%d size=%d\n", start, size)

	w := ctx.Write

	if _, err := w.WriteString(fmt.Sprintf("%d %d%s", start, size, w.Eol)); err != nil {
		return err
	}

	var lines []string

	for i := start; i < start+size; i++ {

		entry := ctx.XRefTable.Table[i]

		if entry.Compressed {
			return errors.New("pdfcpu: writeXRefSubsection: compressed entries present")
		}

		var s string

		if entry.Free {
			s = fmt.Sprintf("%010d %05d f%2s", *entry.Offset, *entry.Generation, w.Eol)
		} else {
			var off int64
			writeOffset, found := ctx.Write.Table[i]
			if found {
				off = writeOffset
			}
			s = fmt.Sprintf("%010d %05d n%2s", off, *entry.Generation, w.Eol)
		}

		lines = append(lines, fmt.Sprintf("%d: %s", i, s))

		if _, err := w.WriteString(s); err != nil {
			return err
		}
	}

	log.Write.Printf("\n%s\n", strings.Join(lines, ""))
	log.Write.Printf("writeXRefSubsection: end\n")

	return nil
}

func deleteRedundantObject(ctx *Context, objNr int) {

	if len(ctx.Write.SelectedPages) == 0 &&
		(ctx.Optimize.IsDuplicateFontObject(objNr) || ctx.Optimize.IsDuplicateImageObject(objNr)) {
		ctx.DeleteObject(objNr)
	}

	if ctx.IsLinearizationObject(objNr) || ctx.Optimize.IsDuplicateInfoObject(objNr) ||
		ctx.Read.IsObjectStreamObject(objNr) || ctx.Read.IsXRefStreamObject(objNr) {
		ctx.DeleteObject(objNr)
	}

}
func deleteRedundantObjects(ctx *Context) {

	if ctx.Optimize == nil {
		return
	}

	xRefTable := ctx.XRefTable

	log.Write.Printf("deleteRedundantObjects begin: Size=%d\n", *xRefTable.Size)

	for i := 0; i < *xRefTable.Size; i++ {

		// Missing object remains missing.
		entry, found := xRefTable.Find(i)
		if !found {
			continue
		}

		// Free object
		if entry.Free {
			continue
		}

		// Object written
		if ctx.Write.HasWriteOffset(i) {
			// Resources may be cross referenced from different objects
			// eg. font descriptors may be shared by different font dicts.
			// Try to remove this object from the list of the potential duplicate objects.
			log.Write.Printf("deleteRedundantObjects: remove duplicate obj #%d\n", i)
			delete(ctx.Optimize.DuplicateFontObjs, i)
			delete(ctx.Optimize.DuplicateImageObjs, i)
			delete(ctx.Optimize.DuplicateInfoObjects, i)
			continue
		}

		// Object not written

		if ctx.Read.Linearized && entry.Offset != nil {
			// This block applies to pre existing objects only.
			// Since there is no type entry for stream dicts associated with linearization dicts
			// we have to check every StreamDict that has not been written.
			if _, ok := entry.Object.(StreamDict); ok {

				if *entry.Offset == *xRefTable.OffsetPrimaryHintTable {
					xRefTable.LinearizationObjs[i] = true
					log.Write.Printf("deleteRedundantObjects: primaryHintTable at obj #%d\n", i)
				}

				if xRefTable.OffsetOverflowHintTable != nil &&
					*entry.Offset == *xRefTable.OffsetOverflowHintTable {
					xRefTable.LinearizationObjs[i] = true
					log.Write.Printf("deleteRedundantObjects: overflowHintTable at obj #%d\n", i)
				}

			}

		}

		deleteRedundantObject(ctx, i)

	}

	log.Write.Println("deleteRedundantObjects end")
}

func sortedWritableKeys(ctx *Context) []int {

	var keys []int

	for i, e := range ctx.Table {
		if e.Free || ctx.Write.HasWriteOffset(i) {
			keys = append(keys, i)
		}
	}

	sort.Ints(keys)

	return keys
}

// After inserting the last object write the cross reference table to disk.
func writeXRefTable(ctx *Context) error {

	if err := ctx.EnsureValidFreeList(); err != nil {
		return err
	}

	keys := sortedWritableKeys(ctx)

	objCount := len(keys)
	log.Write.Printf("xref has %d entries\n", objCount)

	if _, err := ctx.Write.WriteString("xref"); err != nil {
		return err
	}

	if err := ctx.Write.WriteEol(); err != nil {
		return err
	}

	start := keys[0]
	size := 1

	for i := 1; i < len(keys); i++ {

		if keys[i]-keys[i-1] > 1 {

			if err := writeXRefSubsection(ctx, start, size); err != nil {
				return err
			}

			start = keys[i]
			size = 1
			continue
		}

		size++
	}

	if err := writeXRefSubsection(ctx, start, size); err != nil {
		return err
	}

	if err := writeTrailerDict(ctx); err != nil {
		return err
	}

	if err := ctx.Write.WriteEol(); err != nil {
		return err
	}

	if _, err := ctx.Write.WriteString("startxref"); err != nil {
		return err
	}

	if err := ctx.Write.WriteEol(); err != nil {
		return err
	}

	if _, err := ctx.Write.WriteString(fmt.Sprintf("%d", ctx.Write.Offset)); err != nil {
		return err
	}

	return ctx.Write.WriteEol()
}

// int64ToBuf returns a byte slice with length byteCount representing integer i.
func int64ToBuf(i int64, byteCount int) (buf []byte) {

	j := 0
	var b []byte

	for k := i; k > 0; {
		b = append(b, byte(k&0xff))
		k >>= 8
		j++
	}

	// Swap byte order
	for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
		b[i], b[j] = b[j], b[i]
	}

	if j < byteCount {
		buf = append(bytes.Repeat([]byte{0}, byteCount-j), b...)
	} else {
		buf = b
	}

	return
}

func createXRefStream(ctx *Context, i1, i2, i3 int) ([]byte, *Array, error) {

	log.Write.Println("createXRefStream begin")

	xRefTable := ctx.XRefTable

	var (
		buf []byte
		a   Array
	)

	var keys []int
	for i, e := range xRefTable.Table {
		if e.Free || ctx.Write.HasWriteOffset(i) {
			keys = append(keys, i)
		}
	}
	sort.Ints(keys)

	objCount := len(keys)
	log.Write.Printf("createXRefStream: xref has %d entries\n", objCount)

	start := keys[0]
	size := 0

	for i := 0; i < len(keys); i++ {

		j := keys[i]
		entry := xRefTable.Table[j]
		var s1, s2, s3 []byte

		if entry.Free {

			// unused
			log.Write.Printf("createXRefStream: unused i=%d nextFreeAt:%d gen:%d\n", j, int(*entry.Offset), int(*entry.Generation))

			s1 = int64ToBuf(0, i1)
			s2 = int64ToBuf(*entry.Offset, i2)
			s3 = int64ToBuf(int64(*entry.Generation), i3)

		} else if entry.Compressed {

			// in use, compressed into object stream
			log.Write.Printf("createXRefStream: compressed i=%d at objstr %d[%d]\n", j, int(*entry.ObjectStream), int(*entry.ObjectStreamInd))

			s1 = int64ToBuf(2, i1)
			s2 = int64ToBuf(int64(*entry.ObjectStream), i2)
			s3 = int64ToBuf(int64(*entry.ObjectStreamInd), i3)

		} else {

			off, found := ctx.Write.Table[j]
			if !found {
				return nil, nil, errors.Errorf("pdfcpu: createXRefStream: missing write offset for obj #%d\n", i)
			}

			// in use, uncompressed
			log.Write.Printf("createXRefStream: used i=%d offset:%d gen:%d\n", j, int(off), int(*entry.Generation))

			s1 = int64ToBuf(1, i1)
			s2 = int64ToBuf(off, i2)
			s3 = int64ToBuf(int64(*entry.Generation), i3)

		}

		log.Write.Printf("createXRefStream: written: %x %x %x \n", s1, s2, s3)

		buf = append(buf, s1...)
		buf = append(buf, s2...)
		buf = append(buf, s3...)

		if i > 0 && (keys[i]-keys[i-1] > 1) {

			a = append(a, Integer(start))
			a = append(a, Integer(size))

			start = keys[i]
			size = 1
			continue
		}

		size++
	}

	a = append(a, Integer(start))
	a = append(a, Integer(size))

	log.Write.Println("createXRefStream end")

	return buf, &a, nil
}

func writeXRefStream(ctx *Context) error {

	log.Write.Println("writeXRefStream begin")

	xRefTable := ctx.XRefTable
	xRefStreamDict := NewXRefStreamDict(ctx)
	xRefTableEntry := NewXRefTableEntryGen0(*xRefStreamDict)

	// Reuse free objects (including recycled objects from this run).
	objNumber, err := xRefTable.InsertAndUseRecycled(*xRefTableEntry)
	if err != nil {
		return err
	}

	// After the last insert of an object.
	if err = xRefTable.EnsureValidFreeList(); err != nil {
		return err
	}

	xRefStreamDict.Insert("Size", Integer(*xRefTable.Size))

	offset := ctx.Write.Offset

	i2Base := int64(*ctx.Size)
	if offset > i2Base {
		i2Base = offset
	}

	i1 := 1 // 0, 1 or 2 always fit into 1 byte.

	i2 := func(i int64) (byteCount int) {
		for i > 0 {
			i >>= 8
			byteCount++
		}
		return byteCount
	}(i2Base)

	i3 := 2 // scale for max objectstream index <= 0x ff ff

	wArr := Array{Integer(i1), Integer(i2), Integer(i3)}
	xRefStreamDict.Insert("W", wArr)

	// Generate xRefStreamDict data = xref entries -> xRefStreamDict.Content
	content, indArr, err := createXRefStream(ctx, i1, i2, i3)
	if err != nil {
		return err
	}

	xRefStreamDict.Content = content
	xRefStreamDict.Insert("Index", *indArr)

	// Encode xRefStreamDict.Content -> xRefStreamDict.Raw
	if err = xRefStreamDict.StreamDict.Encode(); err != nil {
		return err
	}

	log.Write.Printf("writeXRefStream: xRefStreamDict: %s\n", xRefStreamDict)

	if err = writeStreamDictObject(ctx, objNumber, 0, xRefStreamDict.StreamDict); err != nil {
		return err
	}

	w := ctx.Write

	if _, err = w.WriteString("startxref"); err != nil {
		return err
	}

	if err = w.WriteEol(); err != nil {
		return err
	}

	if _, err = w.WriteString(fmt.Sprintf("%d", offset)); err != nil {
		return err
	}

	if err = w.WriteEol(); err != nil {
		return err
	}

	log.Write.Println("writeXRefStream end")

	return nil
}

func writeEncryptDict(ctx *Context) error {

	// Bail out unless we really have to write encrypted.
	if ctx.Encrypt == nil || ctx.EncKey == nil {
		return nil
	}

	ir := *ctx.Encrypt
	objNumber := int(ir.ObjectNumber)
	genNumber := int(ir.GenerationNumber)

	d, err := ctx.DereferenceDict(ir)
	if err != nil {
		return err
	}

	return writeObject(ctx, objNumber, genNumber, d.PDFString())
}

func setupEncryption(ctx *Context) error {

	var err error

	if ok := validateAlgorithm(ctx); !ok {
		return errors.New("pdfcpu: unsupported encryption algorithm")
	}

	d := newEncryptDict(
		ctx.EncryptUsingAES,
		ctx.EncryptKeyLength,
		ctx.Permissions,
	)

	if ctx.E, err = supportedEncryption(ctx, d); err != nil {
		return err
	}

	if ctx.ID == nil {
		return errors.New("pdfcpu: encrypt: missing ID")
	}

	var id []byte
	if id, err = ctx.IDFirstElement(); err != nil {
		return err
	}

	ctx.E.ID = id

	if err = calcOAndU(ctx, d); err != nil {
		return err
	}

	if err = writePermissions(ctx, d); err != nil {
		return err
	}

	xRefTableEntry := NewXRefTableEntryGen0(d)

	// Reuse free objects (including recycled objects from this run).
	objNumber, err := ctx.InsertAndUseRecycled(*xRefTableEntry)
	if err != nil {
		return err
	}

	ctx.Encrypt = NewIndirectRef(objNumber, 0)

	return nil
}

func updateEncryption(ctx *Context) error {

	d, err := ctx.EncryptDict()
	if err != nil {
		return err
	}

	if ctx.Cmd == SETPERMISSIONS {
		//fmt.Printf("updating permissions to: %v\n", ctx.UserAccessPermissions)
		ctx.E.P = int(ctx.Permissions)
		d.Update("P", Integer(ctx.E.P))
		// and moving on, U is dependent on P
	}

	// ctx.Cmd == CHANGEUPW or CHANGE OPW

	if ctx.UserPWNew != nil {
		//fmt.Printf("change upw from <%s> to <%s>\n", ctx.UserPW, *ctx.UserPWNew)
		ctx.UserPW = *ctx.UserPWNew
	}

	if ctx.OwnerPWNew != nil {
		//fmt.Printf("change opw from <%s> to <%s>\n", ctx.OwnerPW, *ctx.OwnerPWNew)
		ctx.OwnerPW = *ctx.OwnerPWNew
	}

	if ctx.E.R == 5 {

		if err = calcOAndU(ctx, d); err != nil {
			return err
		}

		return writePermissions(ctx, d)
	}

	//fmt.Printf("opw before: length:%d <%s>\n", len(ctx.E.O), ctx.E.O)
	if ctx.E.O, err = o(ctx); err != nil {
		return err
	}
	//fmt.Printf("opw after: length:%d <%s> %0X\n", len(ctx.E.O), ctx.E.O, ctx.E.O)
	d.Update("O", HexLiteral(hex.EncodeToString(ctx.E.O)))

	//fmt.Printf("upw before: length:%d <%s>\n", len(ctx.E.U), ctx.E.U)
	if ctx.E.U, ctx.EncKey, err = u(ctx); err != nil {
		return err
	}
	//fmt.Printf("upw after: length:%d <%s> %0X\n", len(ctx.E.U), ctx.E.U, ctx.E.U)
	//fmt.Printf("encKey = %0X\n", ctx.EncKey)
	d.Update("U", HexLiteral(hex.EncodeToString(ctx.E.U)))

	return nil
}

func handleEncryption(ctx *Context) error {

	if ctx.Cmd == ENCRYPT || ctx.Cmd == DECRYPT {

		if ctx.Cmd == DECRYPT {

			// Remove encryption.
			ctx.EncKey = nil

		} else {

			if err := setupEncryption(ctx); err != nil {
				return err
			}

			alg := "RC4"
			if ctx.EncryptUsingAES {
				alg = "AES"
			}
			log.CLI.Printf("using %s-%d\n", alg, ctx.EncryptKeyLength)
		}

	} else if ctx.UserPWNew != nil || ctx.OwnerPWNew != nil || ctx.Cmd == SETPERMISSIONS {

		if err := updateEncryption(ctx); err != nil {
			return err
		}

	}

	// write xrefstream if using xrefstream only.
	if ctx.Encrypt != nil && ctx.EncKey != nil && !ctx.Read.UsingXRefStreams {
		ctx.WriteObjectStream = false
		ctx.WriteXRefStream = false
	}

	return nil
}

func writeXRef(ctx *Context) error {

	if ctx.WriteXRefStream {
		// Write cross reference stream and generate objectstreams.
		return writeXRefStream(ctx)
	}

	// Write cross reference table section.
	return writeXRefTable(ctx)
}

func setFileSizeOfWrittenFile(w *WriteContext) error {
	if err := w.Flush(); err != nil {
		return err
	}

	// If writing is Writer based then f is nil.
	if w.Fp == nil {
		return nil
	}

	fileInfo, err := w.Fp.Stat()
	if err != nil {
		return err
	}

	w.FileSize = fileInfo.Size()

	return nil
}
