package engine

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"sync"
	"time"

	"github.com/deranjer/gvc/common/database"
)

// ManageFileDiffing handles creating the diffs on the background routines and creating the information
// about each diff that is made.
//
// TODO: fs works however it takes a while to write the diffs to disk. It maybe a better idea to keep the diffs
// in memory (although they could get huge??) and then write them to disk at a later point in time.
// In any event, this works now.
//
// TODO: Be able to cancel a diff creation (for instance if the user resaves). Does this work? Should we block
// creating diffs within 5 minutes of creating one? Cancelling is probably better at this point.
// it might be nice to inform the user when diffs build up
func manageFileDiffing(ctx context.Context, target, diffobject, commitHashPath string, diffChannel chan database.DiffObject, wg *sync.WaitGroup) error {

	var targetHash, diffobjectHash [16]byte
	var err error
	if targetHash, err = UniqueFileHash(target); err != nil {
		return err
	}
	if diffobjectHash, err = UniqueFileHash(diffobject); err != nil {
		return err
	}

	diffTime := time.Now()
	wg.Add(1)
	go func(messages chan<- database.DiffObject) {
		defer wg.Done()
		var dO database.DiffObject
		//doing this on routine to not lose anytime... does it change anything?
		//dO.Description = ""
		dO.Target = target
		dO.DiffObject = diffobject
		dO.StartTime = diffTime
		dO.TargetHash = targetHash //TODO: these being the wrong way round is a legacy thing. Swapping them needs testing, but should be fine
		dO.DiffObjectHash = diffobjectHash
		fmt.Println("creating diff object now")
		if diff, err := binaryDiff(ctx, &dO, commitHashPath); err != nil { //binaryDiff
			fmt.Println("error from binary diff ", err)
			dO.E = err
		} else {
			dO.Diff = &diff //Storing it in memory as a complete failure //TODO: Remove this at some point
		}
		elapsed := time.Since(diffTime)
		dO.Message = "elapsed time:" + elapsed.String()
		messages <- dO
	}(diffChannel)
	return nil
}

//run instead of binaryDiff to turn it off
func dryrun(ctx context.Context, dO *database.DiffObject, commitHash string) ([]byte, error) {
	return []byte{}, nil
}

// Diff manages the creation of the diffs but doesn't actually create the diffs itself.
// Sources are file system sources in this case and an array of diffs (io.Writers) are returned
// 1. This handles whether to save the diffs directly to the drive, and if so, will save to the
// specified location. If so, it will return the diffs.
// 2. Whether to save diffs in both directions
// 3. Creates a diff object that contains any necessary metadata about the diff files
// subject is the file that changed, object is file on record
func binaryDiff(ctx context.Context, dO *database.DiffObject, commitHashPath string) ([]byte, error) {
	var fileName string
	_, fileName = filepath.Split(dO.Target) // dirPath
	startTime := strconv.FormatInt(dO.StartTime.Unix(), 10)
	dO.DiffPath = filepath.Join(commitHashPath, fileName+"_"+startTime) + "_diff.patch"
	if writeDiff, err := os.Create(dO.DiffPath); err != nil {
		return []byte{}, err
	} else if deltaBytes, err := fdeltaDiff(ctx, dO.Target, dO.DiffObject); err != nil {
		return []byte{}, err
	} else {
		bytesWritten, err := writeDiff.Write(deltaBytes)
		if err != nil {
			return []byte{}, err
		}
		dO.DiffSize = int64(bytesWritten)
		return []byte{}, nil
	}

}

//sub is the original
func fdeltaDiff(ctx context.Context, sub, obj string) ([]byte, error) {
	//now follow what is found in fdelta to retrieve the bytes and get back a delta
	//you can use the gob/compression code to save the files according to where in pickle it they are saved
	//TODO: currently the code is used to compress the bsdiff index, but we dont need that, just need to store the
	// delta on disk. This is currently already done somewhere, so can possibly add/swap out the delta and compressor code
	// so that it uses the new code.
	if originalBytes, err := getOriginalBytes(sub); err != nil {
		return []byte{}, err
	} else if deltaBytes, err := createDelta(obj, originalBytes); err != nil {
		return []byte{}, err
	} else if compressedDelta, err := compressDelta(deltaBytes); err != nil {
		return []byte{}, err
	} else {
		return compressedDelta, nil
	}
}
