package dockerfile

import (
	"sync"

	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/builder"
	"github.com/docker/docker/builder/remotecontext"
	"github.com/pkg/errors"
)

// imageContexts is a helper for stacking up built image rootfs and reusing
// them as contexts
type imageContexts struct {
	b     *Builder
	list  []*imageMount
	cache *pathCache
}

type imageMount struct {
	id      string
	ctx     builder.Context
	release func() error
}

func (ic *imageContexts) new() {
	ic.list = append(ic.list, &imageMount{})
}

func (ic *imageContexts) update(imageID string) {
	ic.list[len(ic.list)-1].id = imageID
}

func (ic *imageContexts) validate(i int) error {
	if i < 0 || i >= len(ic.list)-1 {
		var extraMsg string
		if i == len(ic.list)-1 {
			extraMsg = " refers current build block"
		}
		return errors.Errorf("invalid from flag value %d%s", i, extraMsg)
	}
	return nil
}

func (ic *imageContexts) context(i int) (builder.Context, error) {
	if err := ic.validate(i); err != nil {
		return nil, err
	}
	im := ic.list[i]
	if im.ctx == nil {
		if im.id == "" {
			return nil, errors.Errorf("could not copy from empty context")
		}
		p, release, err := ic.b.docker.MountImage(im.id)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to mount %s", im.id)
		}
		ctx, err := remotecontext.NewLazyContext(p)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to create lazycontext for %s", p)
		}
		logrus.Debugf("mounted image: %s %s", im.id, p)
		im.release = release
		im.ctx = ctx
	}
	return im.ctx, nil
}

func (ic *imageContexts) unmount() (retErr error) {
	for _, im := range ic.list {
		if im.release != nil {
			if err := im.release(); err != nil {
				logrus.Error(errors.Wrapf(err, "failed to unmount previous build image"))
				retErr = err
			}
		}
	}
	return
}

func (ic *imageContexts) getCache(i int, path string) (interface{}, bool) {
	if ic.cache != nil {
		im := ic.list[i]
		if im.id == "" {
			return nil, false
		}
		return ic.cache.get(im.id + path)
	}
	return nil, false
}

func (ic *imageContexts) setCache(i int, path string, v interface{}) {
	if ic.cache != nil {
		ic.cache.set(ic.list[i].id+path, v)
	}
}

type pathCache struct {
	mu    sync.Mutex
	items map[string]interface{}
}

func (c *pathCache) set(k string, v interface{}) {
	c.mu.Lock()
	if c.items == nil {
		c.items = make(map[string]interface{})
	}
	c.items[k] = v
	c.mu.Unlock()
}

func (c *pathCache) get(k string) (interface{}, bool) {
	c.mu.Lock()
	if c.items == nil {
		c.mu.Unlock()
		return nil, false
	}
	v, ok := c.items[k]
	c.mu.Unlock()
	return v, ok
}