// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package testing

import (
	"fmt"
	"io"
	"os"
	"runtime"
	"strings"

	"github.com/fatih/color"
	"github.com/mattn/go-colorable"
)

// ConsoleDriver outputs test result to the given stdout/stderr descriptors
type ConsoleDriver struct {
	Stdout   io.Writer
	level    int
	reported bool
	killer   func()
	result   string
}

// NewConsoleDriver initializes and returns a new console driver with output to given file
func NewConsoleDriver(stdout io.Writer) *ConsoleDriver {
	return NewConsoleDriverWithKiller(stdout, func() { os.Exit(1) })
}

// NewConsoleDriverWithKiller initializes and returns a new console driver with output file
// Killer function will be called on fatal errors
func NewConsoleDriverWithKiller(stdout io.Writer, killer func()) *ConsoleDriver {
	// On Windows we must wrap file outputs with a Colorable to achieve the right
	// escape sequences.
	if f, ok := stdout.(*os.File); ok && runtime.GOOS == "windows" {
		stdout = colorable.NewColorable(f)
	}
	return &ConsoleDriver{
		Stdout:   stdout,
		level:    0,
		killer:   killer,
		reported: true,
	}
}

func (d *ConsoleDriver) Run(name string, f func(Driver)) {
	if !d.reported {
		fmt.Fprintln(d.Stdout, "")
	}
	d.printf("%s...", name)

	// Run sub func
	driver := &ConsoleDriver{
		Stdout: d.Stdout,
		level:  d.level + 1,
		killer: d.killer,
	}
	f(driver)

	if !driver.reported {
		color.New(color.FgGreen).Fprintf(driver.Stdout, "OK\n")
		driver.reported = true
	}

	if driver.result != "" {
		driver.Info("result", driver.indent(driver.result))
	}

	d.reported = true
}

func (d *ConsoleDriver) Info(field, value string) {
	if !d.reported {
		fmt.Fprintln(d.Stdout, "")
	}
	d.printf("%s: %s\n", field, value)
	d.reported = true
}

func (d *ConsoleDriver) Warn(field, reason string) {
	if !d.reported {
		fmt.Fprintln(d.Stdout, "")
	}
	d.printf("%s... ", field)
	color.New(color.FgYellow).Fprintf(d.Stdout, "WARN ")
	fmt.Fprintln(d.Stdout, reason)
	d.reported = true
}

func (d *ConsoleDriver) Error(field string, err error) {
	if err == nil {
		d.ok(field)
		return
	}
	d.error(field, err)
}

func (d *ConsoleDriver) Fatal(field string, err error) {
	if err == nil {
		d.ok(field)
		return
	}
	d.error(field, err)
	d.killer()
}

func (d *ConsoleDriver) Result(data string) {
	d.result = data
}

func (d *ConsoleDriver) ok(field string) {
	if !d.reported {
		fmt.Fprintln(d.Stdout, "")
	}
	d.printf("%s... ", field)
	color.New(color.FgGreen).Fprintf(d.Stdout, "OK\n")
	d.reported = true
}

func (d *ConsoleDriver) error(field string, err error) {
	if !d.reported {
		fmt.Fprintln(d.Stdout, "")
	}
	d.printf("%s... ", field)
	color.New(color.FgRed).Fprintf(d.Stdout, "ERROR ")
	fmt.Fprintln(d.Stdout, err.Error())
	d.reported = true
}

func (d *ConsoleDriver) printf(format string, args ...interface{}) {
	for i := 0; i < d.level; i++ {
		fmt.Fprint(d.Stdout, "  ")
	}
	fmt.Fprintf(d.Stdout, format, args...)
}

func (d *ConsoleDriver) indent(data string) string {
	res := "\n"
	for _, line := range strings.Split(data, "\n") {
		res += strings.Repeat(" ", d.level+2) + line + "\n"
	}
	return res
}
