package rice

import (
	"archive/zip"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"

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

// appendedBox defines an appended box
type appendedBox struct {
	Name  string                   // box name
	Files map[string]*appendedFile // appended files (*zip.File) by full path
}

type appendedFile struct {
	zipFile  *zip.File
	dir      bool
	dirInfo  *appendedDirInfo
	children []*appendedFile
	content  []byte
}

// appendedBoxes is a public register of appendes boxes
var appendedBoxes = make(map[string]*appendedBox)

func init() {
	// find if exec is appended
	thisFile, err := osext.Executable()
	if err != nil {
		return // not appended or cant find self executable
	}
	closer, rd, err := zipexe.OpenCloser(thisFile)
	if err != nil {
		return // not appended
	}
	defer closer.Close()

	for _, f := range rd.File {
		// get box and file name from f.Name
		fileParts := strings.SplitN(strings.TrimLeft(filepath.ToSlash(f.Name), "/"), "/", 2)
		boxName := fileParts[0]
		var fileName string
		if len(fileParts) > 1 {
			fileName = fileParts[1]
		}

		// find box or create new one if doesn't exist
		box := appendedBoxes[boxName]
		if box == nil {
			box = &appendedBox{
				Name:  boxName,
				Files: make(map[string]*appendedFile),
			}
			appendedBoxes[boxName] = box
		}

		// create and add file to box
		af := &appendedFile{
			zipFile: f,
		}
		if f.Comment == "dir" {
			af.dir = true
			af.dirInfo = &appendedDirInfo{
				name: filepath.Base(af.zipFile.Name),
				//++ TODO: use zip modtime when that is set correctly: af.zipFile.ModTime()
				time: time.Now(),
			}
		} else {
			// this is a file, we need it's contents so we can create a bytes.Reader when the file is opened
			// make a new byteslice
			af.content = make([]byte, af.zipFile.FileInfo().Size())
			// ignore reading empty files from zip (empty file still is a valid file to be read though!)
			if len(af.content) > 0 {
				// open io.ReadCloser
				rc, err := af.zipFile.Open()
				if err != nil {
					af.content = nil // this will cause an error when the file is being opened or seeked (which is good)
					// TODO: it's quite blunt to just log this stuff. but this is in init, so rice.Debug can't be changed yet..
					log.Printf("error opening appended file %s: %v", af.zipFile.Name, err)
				} else {
					_, err = rc.Read(af.content)
					rc.Close()
					if err != nil {
						af.content = nil // this will cause an error when the file is being opened or seeked (which is good)
						// TODO: it's quite blunt to just log this stuff. but this is in init, so rice.Debug can't be changed yet..
						log.Printf("error reading data for appended file %s: %v", af.zipFile.Name, err)
					}
				}
			}
		}

		// add appendedFile to box file list
		box.Files[fileName] = af

		// add to parent dir (if any)
		dirName := filepath.Dir(fileName)
		if dirName == "." {
			dirName = ""
		}
		if fileName != "" { // don't make box root dir a child of itself
			if dir := box.Files[dirName]; dir != nil {
				dir.children = append(dir.children, af)
			}
		}
	}
}

// implements os.FileInfo.
// used for Readdir()
type appendedDirInfo struct {
	name string
	time time.Time
}

func (adi *appendedDirInfo) Name() string {
	return adi.name
}
func (adi *appendedDirInfo) Size() int64 {
	return 0
}
func (adi *appendedDirInfo) Mode() os.FileMode {
	return os.ModeDir
}
func (adi *appendedDirInfo) ModTime() time.Time {
	return adi.time
}
func (adi *appendedDirInfo) IsDir() bool {
	return true
}
func (adi *appendedDirInfo) Sys() interface{} {
	return nil
}