package resolver

import (
	"bytes"
	"compress/gzip"
	"io"
	"io/ioutil"
	"os"
	"strings"
	"sync"

	"github.com/gobuffalo/packr/v2/file/resolver/encoding/hex"
	"github.com/gobuffalo/packr/v2/plog"

	"github.com/gobuffalo/packr/v2/file"
)

var _ Resolver = &HexGzip{}

type HexGzip struct {
	packed   map[string]string
	unpacked map[string]string
	moot     *sync.RWMutex
}

func (hg HexGzip) String() string {
	return String(&hg)
}

var _ file.FileMappable = &HexGzip{}

func (hg *HexGzip) FileMap() map[string]file.File {
	hg.moot.RLock()
	var names []string
	for k := range hg.packed {
		names = append(names, k)
	}
	hg.moot.RUnlock()
	m := map[string]file.File{}
	for _, n := range names {
		if f, err := hg.Resolve("", n); err == nil {
			m[n] = f
		}
	}
	return m
}

func (hg *HexGzip) Resolve(box string, name string) (file.File, error) {
	plog.Debug(hg, "Resolve", "box", box, "name", name)
	hg.moot.Lock()
	defer hg.moot.Unlock()

	if s, ok := hg.unpacked[name]; ok {
		return file.NewFile(name, []byte(s))
	}
	packed, ok := hg.packed[name]
	if !ok {
		return nil, os.ErrNotExist
	}

	unpacked, err := UnHexGzipString(packed)
	if err != nil {
		return nil, err
	}

	f, err := file.NewFile(OsPath(name), []byte(unpacked))
	if err != nil {
		return nil, err
	}
	hg.unpacked[name] = f.String()
	return f, nil
}

func NewHexGzip(files map[string]string) (*HexGzip, error) {
	if files == nil {
		files = map[string]string{}
	}

	hg := &HexGzip{
		packed:   files,
		unpacked: map[string]string{},
		moot:     &sync.RWMutex{},
	}

	return hg, nil
}

func HexGzipString(s string) (string, error) {
	bb := &bytes.Buffer{}
	enc := hex.NewEncoder(bb)
	zw := gzip.NewWriter(enc)
	io.Copy(zw, strings.NewReader(s))
	zw.Close()

	return bb.String(), nil
}

func UnHexGzipString(packed string) (string, error) {
	br := bytes.NewBufferString(packed)
	dec := hex.NewDecoder(br)
	zr, err := gzip.NewReader(dec)
	if err != nil {
		return "", err
	}
	defer zr.Close()

	b, err := ioutil.ReadAll(zr)
	if err != nil {
		return "", err
	}
	return string(b), nil
}
