// Package contextutil contains functions for working with contexts.
package contextutil

import (
	"context"
	"time"
)

type mergedCtx struct {
	ctx1, ctx2 context.Context

	doneCtx    context.Context
	doneCancel context.CancelFunc
}

// Merge merges two contexts into a single context.
func Merge(ctx1, ctx2 context.Context) (context.Context, context.CancelFunc) {
	mc := &mergedCtx{
		ctx1: ctx1,
		ctx2: ctx2,
	}
	mc.doneCtx, mc.doneCancel = context.WithCancel(context.Background())
	go func() {
		select {
		case <-ctx1.Done():
		case <-ctx2.Done():
		case <-mc.doneCtx.Done():
		}
		mc.doneCancel()
	}()
	return mc, mc.doneCancel
}

func (mc *mergedCtx) Deadline() (deadline time.Time, ok bool) {
	if deadline, ok = mc.ctx1.Deadline(); ok {
		return deadline, ok
	}
	if deadline, ok = mc.ctx2.Deadline(); ok {
		return deadline, ok
	}
	return mc.doneCtx.Deadline()
}

func (mc *mergedCtx) Done() <-chan struct{} {
	return mc.doneCtx.Done()
}

func (mc *mergedCtx) Err() error {
	if err := mc.ctx1.Err(); err != nil {
		return mc.ctx1.Err()
	}
	if err := mc.ctx2.Err(); err != nil {
		return mc.ctx2.Err()
	}
	return mc.doneCtx.Err()
}

func (mc *mergedCtx) Value(key interface{}) interface{} {
	if value := mc.ctx1.Value(key); value != nil {
		return value
	}
	if value := mc.ctx2.Value(key); value != nil {
		return value
	}
	return mc.doneCtx.Value(key)
}