package zap

// Tee creates a Logger that duplicates its log calls to two or more
// loggers. It is similar to io.MultiWriter.
//
// For each logging level method (.Debug, .Info, etc), the Tee calls
// each sub-logger's level method.
//
// Exceptions are made for the Fatal and Panic methods: the returned
// logger calls .Log(FatalLevel, ...) and .Log(PanicLevel, ...). Only
// after all sub-loggers have received the message, then the Tee
// terminates the process (using os.Exit or panic() per usual
// semantics).
//
// DFatal is handled similarly to Fatal and Panic, since it is not actually a
// level; each sub-logger's DFatal method dynamically chooses to either call
// Error or Fatal.
//
// Check returns a CheckedMessage chain of any OK CheckedMessages returned by
// all sub-loggers. The returned message is OK if any of the sub-messages are.
// An exception is made for FatalLevel and PanicLevel, where a CheckedMessage
// is returned against the Tee itself. This is so that tlog.Check(PanicLevel,
// ...).Write(...) is equivalent to tlog.Panic(...) (likewise for FatalLevel).
func Tee(logs ...Logger) Logger {
	switch len(logs) {
	case 0:
		return nil
	case 1:
		return logs[0]
	default:
		return multiLogger(logs)
	}
}

type multiLogger []Logger

func (ml multiLogger) Log(lvl Level, msg string, fields ...Field) {
	ml.log(lvl, msg, fields)
}

func (ml multiLogger) Debug(msg string, fields ...Field) {
	ml.log(DebugLevel, msg, fields)
}

func (ml multiLogger) Info(msg string, fields ...Field) {
	ml.log(InfoLevel, msg, fields)
}

func (ml multiLogger) Warn(msg string, fields ...Field) {
	ml.log(WarnLevel, msg, fields)
}

func (ml multiLogger) Error(msg string, fields ...Field) {
	ml.log(ErrorLevel, msg, fields)
}

func (ml multiLogger) Panic(msg string, fields ...Field) {
	ml.log(PanicLevel, msg, fields)
	panic(msg)
}

func (ml multiLogger) Fatal(msg string, fields ...Field) {
	ml.log(FatalLevel, msg, fields)
	_exit(1)
}

func (ml multiLogger) log(lvl Level, msg string, fields []Field) {
	for _, log := range ml {
		log.Log(lvl, msg, fields...)
	}
}

func (ml multiLogger) DFatal(msg string, fields ...Field) {
	for _, log := range ml {
		log.DFatal(msg, fields...)
	}
}

func (ml multiLogger) With(fields ...Field) Logger {
	clone := make(multiLogger, len(ml))
	for i := range ml {
		clone[i] = ml[i].With(fields...)
	}
	return clone
}

func (ml multiLogger) Check(lvl Level, msg string) *CheckedMessage {
	switch lvl {
	case FatalLevel, PanicLevel:
		// need to end up calling multiLogger Fatal and Panic methods, to avoid
		// sub-logger termination (by merely logging at FatalLevel and
		// PanicLevel).
		return NewCheckedMessage(ml, lvl, msg)
	}
	var cm *CheckedMessage
	for _, log := range ml {
		cm = cm.Chain(log.Check(lvl, msg))
	}
	return cm
}
