// This file is part of arduino-cli.
//
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to license@arduino.cc.

package i18n

import (
	"fmt"
	"io"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"
	"sync"
)

var PLACEHOLDER = regexp.MustCompile("{(\\d)}")

type Logger interface {
	Fprintln(w io.Writer, level string, format string, a ...interface{})
	UnformattedFprintln(w io.Writer, s string)
	UnformattedWrite(w io.Writer, data []byte)
	Println(level string, format string, a ...interface{})
	Name() string
	Flush() string
}

type LoggerToCustomStreams struct {
	Stdout io.Writer
	Stderr io.Writer
	mux    sync.Mutex
}

func (s LoggerToCustomStreams) Fprintln(w io.Writer, level string, format string, a ...interface{}) {
	s.mux.Lock()
	defer s.mux.Unlock()
	target := s.Stdout
	if w == os.Stderr {
		target = s.Stderr
	}
	fmt.Fprintln(target, Format(format, a...))
}

func (s LoggerToCustomStreams) UnformattedFprintln(w io.Writer, str string) {
	s.mux.Lock()
	defer s.mux.Unlock()
	target := s.Stdout
	if w == os.Stderr {
		target = s.Stderr
	}
	fmt.Fprintln(target, str)
}

func (s LoggerToCustomStreams) UnformattedWrite(w io.Writer, data []byte) {
	s.mux.Lock()
	defer s.mux.Unlock()
	target := s.Stdout
	if w == os.Stderr {
		target = s.Stderr
	}
	target.Write(data)
}

func (s LoggerToCustomStreams) Println(level string, format string, a ...interface{}) {
	s.Fprintln(nil, level, format, a...)
}

func (s LoggerToCustomStreams) Flush() string {
	return ""
}

func (s LoggerToCustomStreams) Name() string {
	return "LoggerToCustomStreams"
}

type NoopLogger struct{}

func (s NoopLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {}

func (s NoopLogger) UnformattedFprintln(w io.Writer, str string) {}

func (s NoopLogger) UnformattedWrite(w io.Writer, data []byte) {}

func (s NoopLogger) Println(level string, format string, a ...interface{}) {}

func (s NoopLogger) Flush() string {
	return ""
}

func (s NoopLogger) Name() string {
	return "noop"
}

type AccumulatorLogger struct {
	Buffer *[]string
}

func (s AccumulatorLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {
	*s.Buffer = append(*s.Buffer, Format(format, a...))
}

func (s AccumulatorLogger) UnformattedFprintln(w io.Writer, str string) {
	*s.Buffer = append(*s.Buffer, str)
}

func (s AccumulatorLogger) UnformattedWrite(w io.Writer, data []byte) {
	*s.Buffer = append(*s.Buffer, string(data))
}

func (s AccumulatorLogger) Println(level string, format string, a ...interface{}) {
	s.Fprintln(nil, level, format, a...)
}

func (s AccumulatorLogger) Flush() string {
	str := strings.Join(*s.Buffer, "\n")
	*s.Buffer = (*s.Buffer)[0:0]
	return str
}

func (s AccumulatorLogger) Name() string {
	return "accumulator"
}

type HumanTagsLogger struct{}

func (s HumanTagsLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {
	format = "[" + level + "] " + format
	fprintln(w, Format(format, a...))
}

func (s HumanTagsLogger) Println(level string, format string, a ...interface{}) {
	s.Fprintln(os.Stdout, level, format, a...)
}

func (s HumanTagsLogger) UnformattedFprintln(w io.Writer, str string) {
	fprintln(w, str)
}

func (s HumanTagsLogger) UnformattedWrite(w io.Writer, data []byte) {
	write(w, data)
}

func (s HumanTagsLogger) Flush() string {
	return ""
}

func (s HumanTagsLogger) Name() string {
	return "humantags"
}

type HumanLogger struct{}

func (s HumanLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {
	fprintln(w, Format(format, a...))
}

func (s HumanLogger) Println(level string, format string, a ...interface{}) {
	s.Fprintln(os.Stdout, level, format, a...)
}

func (s HumanLogger) UnformattedFprintln(w io.Writer, str string) {
	fprintln(w, str)
}

func (s HumanLogger) UnformattedWrite(w io.Writer, data []byte) {
	write(w, data)
}

func (s HumanLogger) Flush() string {
	return ""
}

func (s HumanLogger) Name() string {
	return "human"
}

type MachineLogger struct{}

func (s MachineLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {
	printMachineFormattedLogLine(w, level, format, a)
}

func (s MachineLogger) Println(level string, format string, a ...interface{}) {
	printMachineFormattedLogLine(os.Stdout, level, format, a)
}

func (s MachineLogger) UnformattedFprintln(w io.Writer, str string) {
	fprintln(w, str)
}

func (s MachineLogger) Flush() string {
	return ""
}

func (s MachineLogger) Name() string {
	return "machine"
}

func (s MachineLogger) UnformattedWrite(w io.Writer, data []byte) {
	write(w, data)
}

func printMachineFormattedLogLine(w io.Writer, level string, format string, a []interface{}) {
	a = append([]interface{}(nil), a...)
	for idx, value := range a {
		if str, ok := value.(string); ok {
			a[idx] = url.QueryEscape(str)
		} else if stringer, ok := value.(fmt.Stringer); ok {
			a[idx] = url.QueryEscape(stringer.String())
		}
	}
	fprintf(w, "===%s ||| %s ||| %s\n", level, format, a)
}

var lock sync.Mutex

func fprintln(w io.Writer, s string) {
	lock.Lock()
	defer lock.Unlock()
	fmt.Fprintln(w, s)
}

func write(w io.Writer, data []byte) {
	lock.Lock()
	defer lock.Unlock()
	w.Write(data)
}

func fprintf(w io.Writer, format string, a ...interface{}) {
	lock.Lock()
	defer lock.Unlock()
	fmt.Fprintf(w, format, a...)
}

func FromJavaToGoSyntax(s string) string {
	submatches := PLACEHOLDER.FindAllStringSubmatch(s, -1)
	for _, submatch := range submatches {
		idx, err := strconv.Atoi(submatch[1])
		if err != nil {
			panic(err)
		}
		idx = idx + 1
		s = strings.Replace(s, submatch[0], "%["+strconv.Itoa(idx)+"]v", -1)
	}

	s = strings.Replace(s, "''", "'", -1)

	return s
}

func Format(format string, a ...interface{}) string {
	format = FromJavaToGoSyntax(format)
	message := fmt.Sprintf(format, a...)
	return message
}
