package layer

import (
	"io"
	"sync"

	"github.com/docker/docker/pkg/archive"
)

type mountedLayer struct {
	name       string
	mountID    string
	initID     string
	parent     *roLayer
	path       string
	layerStore *layerStore

	references map[RWLayer]*referencedRWLayer
}

func (ml *mountedLayer) cacheParent() string {
	if ml.initID != "" {
		return ml.initID
	}
	if ml.parent != nil {
		return ml.parent.cacheID
	}
	return ""
}

func (ml *mountedLayer) TarStream() (io.ReadCloser, error) {
	archiver, err := ml.layerStore.driver.Diff(ml.mountID, ml.cacheParent())
	if err != nil {
		return nil, err
	}
	return archiver, nil
}

func (ml *mountedLayer) Name() string {
	return ml.name
}

func (ml *mountedLayer) Parent() Layer {
	if ml.parent != nil {
		return ml.parent
	}

	// Return a nil interface instead of an interface wrapping a nil
	// pointer.
	return nil
}

func (ml *mountedLayer) Mount(mountLabel string) (string, error) {
	return ml.layerStore.driver.Get(ml.mountID, mountLabel)
}

func (ml *mountedLayer) Unmount() error {
	return ml.layerStore.driver.Put(ml.mountID)
}

func (ml *mountedLayer) Size() (int64, error) {
	return ml.layerStore.driver.DiffSize(ml.mountID, ml.cacheParent())
}

func (ml *mountedLayer) Changes() ([]archive.Change, error) {
	return ml.layerStore.driver.Changes(ml.mountID, ml.cacheParent())
}

func (ml *mountedLayer) Metadata() (map[string]string, error) {
	return ml.layerStore.driver.GetMetadata(ml.mountID)
}

func (ml *mountedLayer) getReference() RWLayer {
	ref := &referencedRWLayer{
		mountedLayer: ml,
	}
	ml.references[ref] = ref

	return ref
}

func (ml *mountedLayer) hasReferences() bool {
	return len(ml.references) > 0
}

func (ml *mountedLayer) incActivityCount(ref RWLayer) error {
	rl, ok := ml.references[ref]
	if !ok {
		return ErrLayerNotRetained
	}

	if err := rl.acquire(); err != nil {
		return err
	}
	return nil
}

func (ml *mountedLayer) deleteReference(ref RWLayer) error {
	rl, ok := ml.references[ref]
	if !ok {
		return ErrLayerNotRetained
	}

	if err := rl.release(); err != nil {
		return err
	}
	delete(ml.references, ref)

	return nil
}

func (ml *mountedLayer) retakeReference(r RWLayer) {
	if ref, ok := r.(*referencedRWLayer); ok {
		ref.activityCount = 0
		ml.references[ref] = ref
	}
}

type referencedRWLayer struct {
	*mountedLayer

	activityL     sync.Mutex
	activityCount int
}

func (rl *referencedRWLayer) acquire() error {
	rl.activityL.Lock()
	defer rl.activityL.Unlock()

	rl.activityCount++

	return nil
}

func (rl *referencedRWLayer) release() error {
	rl.activityL.Lock()
	defer rl.activityL.Unlock()

	if rl.activityCount > 0 {
		return ErrActiveMount
	}

	rl.activityCount = -1

	return nil
}

func (rl *referencedRWLayer) Mount(mountLabel string) (string, error) {
	rl.activityL.Lock()
	defer rl.activityL.Unlock()

	if rl.activityCount == -1 {
		return "", ErrLayerNotRetained
	}

	if rl.activityCount > 0 {
		rl.activityCount++
		return rl.path, nil
	}

	m, err := rl.mountedLayer.Mount(mountLabel)
	if err == nil {
		rl.activityCount++
		rl.path = m
	}
	return m, err
}

// Unmount decrements the activity count and unmounts the underlying layer
// Callers should only call `Unmount` once per call to `Mount`, even on error.
func (rl *referencedRWLayer) Unmount() error {
	rl.activityL.Lock()
	defer rl.activityL.Unlock()

	if rl.activityCount == 0 {
		return ErrNotMounted
	}
	if rl.activityCount == -1 {
		return ErrLayerNotRetained
	}

	rl.activityCount--
	if rl.activityCount > 0 {
		return nil
	}

	return rl.mountedLayer.Unmount()
}
