package main

import (
	"archive/zip"
	"fmt"
	"go/build"
	"io"
	"os"
	"path/filepath"
	"strings"
	"time"

	zipexe "github.com/daaku/go.zipexe"
)

func operationAppend(pkgs []*build.Package) {
	// create tmp zipfile
	tmpZipfileName := filepath.Join(os.TempDir(), fmt.Sprintf("ricebox-%d-%s.zip", time.Now().Unix(), randomString(10)))
	verbosef("Will create tmp zipfile: %s\n", tmpZipfileName)
	tmpZipfile, err := os.Create(tmpZipfileName)
	if err != nil {
		fmt.Printf("Error creating tmp zipfile: %s\n", err)
		os.Exit(1)
	}
	defer func() {
		tmpZipfile.Close()
		os.Remove(tmpZipfileName)
	}()

	// find abs path for binary file
	binfileName, err := filepath.Abs(flags.Append.Executable)
	if err != nil {
		fmt.Printf("Error finding absolute path for executable to append: %s\n", err)
		os.Exit(1)
	}
	verbosef("Will append to file: %s\n", binfileName)

	// check that command doesn't already have zip appended
	if rd, _ := zipexe.Open(binfileName); rd != nil {
		fmt.Printf("Cannot append to already appended executable. Please remove %s and build a fresh one.\n", binfileName)
		os.Exit(1)
	}

	// open binfile
	binfile, err := os.OpenFile(binfileName, os.O_WRONLY, os.ModeAppend)
	if err != nil {
		fmt.Printf("Error: unable to open executable file: %s\n", err)
		os.Exit(1)
	}
	defer binfile.Close()

	binfileInfo, err := binfile.Stat()
	if err != nil {
		fmt.Printf("Error: unable to stat executable file: %s\n", err)
		os.Exit(1)
	}

	// create zip.Writer
	zipWriter := zip.NewWriter(tmpZipfile)

	// write the zip offset into the zip data
	zipWriter.SetOffset(binfileInfo.Size())

	for _, pkg := range pkgs {
		// find boxes for this command
		boxMap := findBoxes(pkg)

		// notify user when no calls to rice.FindBox are made (is this an error and therefore os.Exit(1) ?
		if len(boxMap) == 0 {
			fmt.Printf("no calls to rice.FindBox() or rice.MustFindBox() found in import path `%s`\n", pkg.ImportPath)
			continue
		}

		verbosef("\n")

		for boxname := range boxMap {
			appendedBoxName := strings.Replace(boxname, `/`, `-`, -1)

			// walk box path's and insert files
			boxPath := filepath.Clean(filepath.Join(pkg.Dir, boxname))
			filepath.Walk(boxPath, func(path string, info os.FileInfo, err error) error {
				if info == nil {
					fmt.Printf("Error: box \"%s\" not found on disk\n", path)
					os.Exit(1)
				}
				// create zipFilename
				zipFileName := filepath.Join(appendedBoxName, strings.TrimPrefix(path, boxPath))
				// write directories as empty file with comment "dir"
				if info.IsDir() {
					header := &zip.FileHeader{
						Name:     zipFileName,
						Comment:  "dir",
					}
					header.SetModTime(info.ModTime())
					_, err := zipWriter.CreateHeader(header)
					if err != nil {
						fmt.Printf("Error creating dir in tmp zip: %s\n", err)
						os.Exit(1)
					}
					return nil
				}

				// create zipFileWriter
				zipFileHeader, err := zip.FileInfoHeader(info)
				if err != nil {
					fmt.Printf("Error creating zip FileHeader: %v\n", err)
					os.Exit(1)
				}
				zipFileHeader.Name = zipFileName
				zipFileWriter, err := zipWriter.CreateHeader(zipFileHeader)
				if err != nil {
					fmt.Printf("Error creating file in tmp zip: %s\n", err)
					os.Exit(1)
				}
				srcFile, err := os.Open(path)
				if err != nil {
					fmt.Printf("Error opening file to append: %s\n", err)
					os.Exit(1)
				}
				_, err = io.Copy(zipFileWriter, srcFile)
				if err != nil {
					fmt.Printf("Error copying file contents to zip: %s\n", err)
					os.Exit(1)
				}
				srcFile.Close()

				return nil
			})
		}
	}

	err = zipWriter.Close()
	if err != nil {
		fmt.Printf("Error closing tmp zipfile: %s\n", err)
		os.Exit(1)
	}

	err = tmpZipfile.Sync()
	if err != nil {
		fmt.Printf("Error syncing tmp zipfile: %s\n", err)
		os.Exit(1)
	}
	_, err = tmpZipfile.Seek(0, 0)
	if err != nil {
		fmt.Printf("Error seeking tmp zipfile: %s\n", err)
		os.Exit(1)
	}
	_, err = binfile.Seek(0, 2)
	if err != nil {
		fmt.Printf("Error seeking bin file: %s\n", err)
		os.Exit(1)
	}

	_, err = io.Copy(binfile, tmpZipfile)
	if err != nil {
		fmt.Printf("Error appending zipfile to executable: %s\n", err)
		os.Exit(1)
	}
}
