// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed 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.

// +build windows

package logs

import (
	"bytes"
	"fmt"
	"syscall"
	"testing"
)

var GetConsoleScreenBufferInfo = getConsoleScreenBufferInfo

func ChangeColor(color uint16) {
	setConsoleTextAttribute(uintptr(syscall.Stdout), color)
}

func ResetColor() {
	ChangeColor(uint16(0x0007))
}

func TestWritePlanText(t *testing.T) {
	inner := bytes.NewBufferString("")
	w := NewAnsiColorWriter(inner)
	expected := "plain text"
	fmt.Fprintf(w, expected)
	actual := inner.String()
	if actual != expected {
		t.Errorf("Get %q, want %q", actual, expected)
	}
}

func TestWriteParseText(t *testing.T) {
	inner := bytes.NewBufferString("")
	w := NewAnsiColorWriter(inner)

	inputTail := "\x1b[0mtail text"
	expectedTail := "tail text"
	fmt.Fprintf(w, inputTail)
	actualTail := inner.String()
	inner.Reset()
	if actualTail != expectedTail {
		t.Errorf("Get %q, want %q", actualTail, expectedTail)
	}

	inputHead := "head text\x1b[0m"
	expectedHead := "head text"
	fmt.Fprintf(w, inputHead)
	actualHead := inner.String()
	inner.Reset()
	if actualHead != expectedHead {
		t.Errorf("Get %q, want %q", actualHead, expectedHead)
	}

	inputBothEnds := "both ends \x1b[0m text"
	expectedBothEnds := "both ends  text"
	fmt.Fprintf(w, inputBothEnds)
	actualBothEnds := inner.String()
	inner.Reset()
	if actualBothEnds != expectedBothEnds {
		t.Errorf("Get %q, want %q", actualBothEnds, expectedBothEnds)
	}

	inputManyEsc := "\x1b\x1b\x1b\x1b[0m many esc"
	expectedManyEsc := "\x1b\x1b\x1b many esc"
	fmt.Fprintf(w, inputManyEsc)
	actualManyEsc := inner.String()
	inner.Reset()
	if actualManyEsc != expectedManyEsc {
		t.Errorf("Get %q, want %q", actualManyEsc, expectedManyEsc)
	}

	expectedSplit := "split  text"
	for _, ch := range "split \x1b[0m text" {
		fmt.Fprintf(w, string(ch))
	}
	actualSplit := inner.String()
	inner.Reset()
	if actualSplit != expectedSplit {
		t.Errorf("Get %q, want %q", actualSplit, expectedSplit)
	}
}

type screenNotFoundError struct {
	error
}

func writeAnsiColor(expectedText, colorCode string) (actualText string, actualAttributes uint16, err error) {
	inner := bytes.NewBufferString("")
	w := NewAnsiColorWriter(inner)
	fmt.Fprintf(w, "\x1b[%sm%s", colorCode, expectedText)

	actualText = inner.String()
	screenInfo := GetConsoleScreenBufferInfo(uintptr(syscall.Stdout))
	if screenInfo != nil {
		actualAttributes = screenInfo.WAttributes
	} else {
		err = &screenNotFoundError{}
	}
	return
}

type testParam struct {
	text       string
	attributes uint16
	ansiColor  string
}

func TestWriteAnsiColorText(t *testing.T) {
	screenInfo := GetConsoleScreenBufferInfo(uintptr(syscall.Stdout))
	if screenInfo == nil {
		t.Fatal("Could not get ConsoleScreenBufferInfo")
	}
	defer ChangeColor(screenInfo.WAttributes)
	defaultFgColor := screenInfo.WAttributes & uint16(0x0007)
	defaultBgColor := screenInfo.WAttributes & uint16(0x0070)
	defaultFgIntensity := screenInfo.WAttributes & uint16(0x0008)
	defaultBgIntensity := screenInfo.WAttributes & uint16(0x0080)

	fgParam := []testParam{
		{"foreground black  ", uint16(0x0000 | 0x0000), "30"},
		{"foreground red    ", uint16(0x0004 | 0x0000), "31"},
		{"foreground green  ", uint16(0x0002 | 0x0000), "32"},
		{"foreground yellow ", uint16(0x0006 | 0x0000), "33"},
		{"foreground blue   ", uint16(0x0001 | 0x0000), "34"},
		{"foreground magenta", uint16(0x0005 | 0x0000), "35"},
		{"foreground cyan   ", uint16(0x0003 | 0x0000), "36"},
		{"foreground white  ", uint16(0x0007 | 0x0000), "37"},
		{"foreground default", defaultFgColor | 0x0000, "39"},
		{"foreground light gray   ", uint16(0x0000 | 0x0008 | 0x0000), "90"},
		{"foreground light red    ", uint16(0x0004 | 0x0008 | 0x0000), "91"},
		{"foreground light green  ", uint16(0x0002 | 0x0008 | 0x0000), "92"},
		{"foreground light yellow ", uint16(0x0006 | 0x0008 | 0x0000), "93"},
		{"foreground light blue   ", uint16(0x0001 | 0x0008 | 0x0000), "94"},
		{"foreground light magenta", uint16(0x0005 | 0x0008 | 0x0000), "95"},
		{"foreground light cyan   ", uint16(0x0003 | 0x0008 | 0x0000), "96"},
		{"foreground light white  ", uint16(0x0007 | 0x0008 | 0x0000), "97"},
	}

	bgParam := []testParam{
		{"background black  ", uint16(0x0007 | 0x0000), "40"},
		{"background red    ", uint16(0x0007 | 0x0040), "41"},
		{"background green  ", uint16(0x0007 | 0x0020), "42"},
		{"background yellow ", uint16(0x0007 | 0x0060), "43"},
		{"background blue   ", uint16(0x0007 | 0x0010), "44"},
		{"background magenta", uint16(0x0007 | 0x0050), "45"},
		{"background cyan   ", uint16(0x0007 | 0x0030), "46"},
		{"background white  ", uint16(0x0007 | 0x0070), "47"},
		{"background default", uint16(0x0007) | defaultBgColor, "49"},
		{"background light gray   ", uint16(0x0007 | 0x0000 | 0x0080), "100"},
		{"background light red    ", uint16(0x0007 | 0x0040 | 0x0080), "101"},
		{"background light green  ", uint16(0x0007 | 0x0020 | 0x0080), "102"},
		{"background light yellow ", uint16(0x0007 | 0x0060 | 0x0080), "103"},
		{"background light blue   ", uint16(0x0007 | 0x0010 | 0x0080), "104"},
		{"background light magenta", uint16(0x0007 | 0x0050 | 0x0080), "105"},
		{"background light cyan   ", uint16(0x0007 | 0x0030 | 0x0080), "106"},
		{"background light white  ", uint16(0x0007 | 0x0070 | 0x0080), "107"},
	}

	resetParam := []testParam{
		{"all reset", defaultFgColor | defaultBgColor | defaultFgIntensity | defaultBgIntensity, "0"},
		{"all reset", defaultFgColor | defaultBgColor | defaultFgIntensity | defaultBgIntensity, ""},
	}

	boldParam := []testParam{
		{"bold on", uint16(0x0007 | 0x0008), "1"},
		{"bold off", uint16(0x0007), "21"},
	}

	underscoreParam := []testParam{
		{"underscore on", uint16(0x0007 | 0x8000), "4"},
		{"underscore off", uint16(0x0007), "24"},
	}

	blinkParam := []testParam{
		{"blink on", uint16(0x0007 | 0x0080), "5"},
		{"blink off", uint16(0x0007), "25"},
	}

	mixedParam := []testParam{
		{"both black,   bold, underline, blink", uint16(0x0000 | 0x0000 | 0x0008 | 0x8000 | 0x0080), "30;40;1;4;5"},
		{"both red,     bold, underline, blink", uint16(0x0004 | 0x0040 | 0x0008 | 0x8000 | 0x0080), "31;41;1;4;5"},
		{"both green,   bold, underline, blink", uint16(0x0002 | 0x0020 | 0x0008 | 0x8000 | 0x0080), "32;42;1;4;5"},
		{"both yellow,  bold, underline, blink", uint16(0x0006 | 0x0060 | 0x0008 | 0x8000 | 0x0080), "33;43;1;4;5"},
		{"both blue,    bold, underline, blink", uint16(0x0001 | 0x0010 | 0x0008 | 0x8000 | 0x0080), "34;44;1;4;5"},
		{"both magenta, bold, underline, blink", uint16(0x0005 | 0x0050 | 0x0008 | 0x8000 | 0x0080), "35;45;1;4;5"},
		{"both cyan,    bold, underline, blink", uint16(0x0003 | 0x0030 | 0x0008 | 0x8000 | 0x0080), "36;46;1;4;5"},
		{"both white,   bold, underline, blink", uint16(0x0007 | 0x0070 | 0x0008 | 0x8000 | 0x0080), "37;47;1;4;5"},
		{"both default, bold, underline, blink", uint16(defaultFgColor | defaultBgColor | 0x0008 | 0x8000 | 0x0080), "39;49;1;4;5"},
	}

	assertTextAttribute := func(expectedText string, expectedAttributes uint16, ansiColor string) {
		actualText, actualAttributes, err := writeAnsiColor(expectedText, ansiColor)
		if actualText != expectedText {
			t.Errorf("Get %q, want %q", actualText, expectedText)
		}
		if err != nil {
			t.Fatal("Could not get ConsoleScreenBufferInfo")
		}
		if actualAttributes != expectedAttributes {
			t.Errorf("Text: %q, Get 0x%04x, want 0x%04x", expectedText, actualAttributes, expectedAttributes)
		}
	}

	for _, v := range fgParam {
		ResetColor()
		assertTextAttribute(v.text, v.attributes, v.ansiColor)
	}

	for _, v := range bgParam {
		ChangeColor(uint16(0x0070 | 0x0007))
		assertTextAttribute(v.text, v.attributes, v.ansiColor)
	}

	for _, v := range resetParam {
		ChangeColor(uint16(0x0000 | 0x0070 | 0x0008))
		assertTextAttribute(v.text, v.attributes, v.ansiColor)
	}

	ResetColor()
	for _, v := range boldParam {
		assertTextAttribute(v.text, v.attributes, v.ansiColor)
	}

	ResetColor()
	for _, v := range underscoreParam {
		assertTextAttribute(v.text, v.attributes, v.ansiColor)
	}

	ResetColor()
	for _, v := range blinkParam {
		assertTextAttribute(v.text, v.attributes, v.ansiColor)
	}

	for _, v := range mixedParam {
		ResetColor()
		assertTextAttribute(v.text, v.attributes, v.ansiColor)
	}
}

func TestIgnoreUnknownSequences(t *testing.T) {
	inner := bytes.NewBufferString("")
	w := NewModeAnsiColorWriter(inner, OutputNonColorEscSeq)

	inputText := "\x1b[=decpath mode"
	expectedTail := inputText
	fmt.Fprintf(w, inputText)
	actualTail := inner.String()
	inner.Reset()
	if actualTail != expectedTail {
		t.Errorf("Get %q, want %q", actualTail, expectedTail)
	}

	inputText = "\x1b[=tailing esc and bracket\x1b["
	expectedTail = inputText
	fmt.Fprintf(w, inputText)
	actualTail = inner.String()
	inner.Reset()
	if actualTail != expectedTail {
		t.Errorf("Get %q, want %q", actualTail, expectedTail)
	}

	inputText = "\x1b[?tailing esc\x1b"
	expectedTail = inputText
	fmt.Fprintf(w, inputText)
	actualTail = inner.String()
	inner.Reset()
	if actualTail != expectedTail {
		t.Errorf("Get %q, want %q", actualTail, expectedTail)
	}

	inputText = "\x1b[1h;3punended color code invalid\x1b3"
	expectedTail = inputText
	fmt.Fprintf(w, inputText)
	actualTail = inner.String()
	inner.Reset()
	if actualTail != expectedTail {
		t.Errorf("Get %q, want %q", actualTail, expectedTail)
	}
}