// Copyright 2017 Pilosa Corp.
//
// 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.

package pql_test

import (
	"encoding/json"
	"reflect"
	"strings"
	"testing"

	"github.com/pilosa/pilosa/v2/pql"
)

// Ensure call can be converted into a string.
func TestDecimal(t *testing.T) {
	t.Run("Parse", func(t *testing.T) {
		tests := []struct {
			s      string
			exp    pql.Decimal
			expErr string
		}{
			{"0", pql.Decimal{0, 0}, ""},
			{"-0", pql.Decimal{0, 0}, ""},
			{"0.0", pql.Decimal{0, 0}, ""},
			{"0.", pql.Decimal{0, 0}, ""},
			{"-0.00", pql.Decimal{0, 0}, ""},
			{"123.4567", pql.Decimal{1234567, 4}, ""},
			{"123.456700", pql.Decimal{1234567, 4}, ""},
			{"00123.4567", pql.Decimal{1234567, 4}, ""},
			{"+123.4567", pql.Decimal{1234567, 4}, ""},
			{"-123.4567", pql.Decimal{-1234567, 4}, ""},
			{"-00123.4567", pql.Decimal{-1234567, 4}, ""},
			{"-12.25", pql.Decimal{-1225, 2}, ""},

			{"123", pql.Decimal{123, 0}, ""},
			{"-12300", pql.Decimal{-123, -2}, ""},
			{"+012300", pql.Decimal{123, -2}, ""},
			{"12300", pql.Decimal{123, -2}, ""},
			{"12300.", pql.Decimal{123, -2}, ""},
			{"12300.0", pql.Decimal{123, -2}, ""},
			{"123.0", pql.Decimal{123, 0}, ""},

			{".123", pql.Decimal{123, 3}, ""},
			{"0.123", pql.Decimal{123, 3}, ""},
			{"0.001230", pql.Decimal{123, 5}, ""},
			{"-0.001230", pql.Decimal{-123, 5}, ""},

			// int64 edges.
			{".000009223372036854775807", pql.Decimal{9223372036854775807, 24}, ""},
			{"-.000009223372036854775808", pql.Decimal{-9223372036854775808, 24}, ""},
			{"92233720368547.75807", pql.Decimal{9223372036854775807, 5}, ""},
			{"-92233720368547.75807", pql.Decimal{-9223372036854775807, 5}, ""},
			{"9223372036854775807000", pql.Decimal{9223372036854775807, -3}, ""},
			{"-9223372036854775807000", pql.Decimal{-9223372036854775807, -3}, ""},

			// precision adjustment
			{"2.666666666666666667", pql.Decimal{2666666666666666667, 18}, ""},
			{"2.6666666666666666667", pql.Decimal{2666666666666666666, 18}, ""},
			{"2.6666666666666666666667", pql.Decimal{2666666666666666666, 18}, ""},
			{"-9.223372036854775808", pql.Decimal{-9223372036854775808, 18}, ""},
			{"-9.223372036854775809", pql.Decimal{-922337203685477580, 17}, ""},
			{"9.223372036854775807", pql.Decimal{9223372036854775807, 18}, ""},
			{"9.223372036854775808", pql.Decimal{922337203685477580, 17}, ""},

			// Error cases.
			{"", pql.Decimal{}, "decimal string is empty"},
			{"-", pql.Decimal{}, "decimal string is empty"},
			{"*0.123", pql.Decimal{}, "invalid syntax"},
			{"abc", pql.Decimal{}, "invalid syntax"},
			{"0.12.3", pql.Decimal{}, "invalid decimal string"},
			{"--12300", pql.Decimal{}, "invalid syntax"},
			{"  123.4567  ", pql.Decimal{}, "invalid syntax"},
			{"  123.4567", pql.Decimal{}, "invalid syntax"},
			{"123.4567 ", pql.Decimal{}, "invalid syntax"},
			{"0.a", pql.Decimal{}, "invalid syntax"},

			// These are no longer error cases since we introduced precision adjustment.
			//{"922337203685477580.9", pql.Decimal{}, "value out of range"},
			//{"-922337203685477580.9", pql.Decimal{}, "value out of range"},
			{"9223372036854775808000", pql.Decimal{}, "value out of range"},
			{"-9223372036854775809000", pql.Decimal{}, "value out of range"},
		}
		for i, test := range tests {
			dec, err := pql.ParseDecimal(test.s)
			if test.expErr != "" {
				if err == nil || !strings.Contains(err.Error(), test.expErr) {
					t.Fatalf("test %d parsing string `%s`: expected error to contain: %s, but got: %v", i, test.s, test.expErr, err)
				}
			} else if err != nil {
				t.Fatalf("test %d parsing string `%s`: %s", i, test.s, err)
			} else if dec != test.exp {
				t.Fatalf("test %d parsing string `%s`: expected: %v, but got: %v", i, test.s, test.exp, dec)
			}
		}
	})

	t.Run("ToInt64", func(t *testing.T) {
		tests := []struct {
			dec   pql.Decimal
			scale int64
			exp   int64
		}{
			{pql.Decimal{0, 0}, 0, 0},  // 0 : 0
			{pql.Decimal{0, 0}, 1, 0},  // 0 : 0.0
			{pql.Decimal{0, 0}, -1, 0}, // 0 : 0

			{pql.Decimal{1234567, 4}, 5, 12345670}, // 123.4567 : 123.45670
			{pql.Decimal{1234567, 4}, 4, 1234567},  // 123.4567 : 123.4567
			{pql.Decimal{1234567, 4}, 3, 123456},   // 123.4567 : 123.456

			{pql.Decimal{-1234567, 4}, 5, -12345670}, // -123.4567 : -123.45670
			{pql.Decimal{-1234567, 4}, 4, -1234567},  // -123.4567 : -123.4567
			{pql.Decimal{-1234567, 4}, 3, -123456},   // -123.4567 : -123.456

			{pql.Decimal{123, -2}, 5, 1230000000}, // 12300 : 12300.00000
			{pql.Decimal{123, -2}, -1, 1230},      // 12300 : 1230
			{pql.Decimal{123, 1}, -1, 1},          // 12.3 : 1
			{pql.Decimal{123, 1}, -2, 0},          // 12.3 : 0
		}
		for i, test := range tests {
			v := test.dec.ToInt64(test.scale)
			if v != test.exp {
				t.Fatalf("test %d expected: %d, but got: %d", i, test.exp, v)
			}
		}
	})

	t.Run("String", func(t *testing.T) {
		tests := []struct {
			s   string
			exp string
		}{
			{"123.4567", "123.4567"},
			{"123.456700", "123.4567"},
			{"00123.4567", "123.4567"},
			{"+123.4567", "123.4567"},
			{"-123.4567", "-123.4567"},
			{"-00123.4567", "-123.4567"},
			{"-12.25", "-12.25"},

			{"123", "123"},
			{"-12300", "-12300"},
			{"+012300", "12300"},
			{"12300", "12300"},

			{"12300.", "12300"},
			{"12300.0", "12300"},
			{"123.0", "123"},

			{"0.123", "0.123"},
			{"0.001230", "0.00123"},
			{"+0.001230", "0.00123"},
			{"-0.001230", "-0.00123"},
		}
		for i, test := range tests {
			dec, err := pql.ParseDecimal(test.s)
			if err != nil {
				t.Fatalf("parsing string `%s`: %s", test.s, err)
			}
			if str := dec.String(); str != test.exp {
				t.Fatalf("test %d expected: %s, but got: %s", i, test.exp, str)
			}
		}
	})

	t.Run("Comparisons", func(t *testing.T) {
		tests := []struct {
			d1     pql.Decimal
			d2     pql.Decimal
			expLT  bool
			expLTE bool
			expGT  bool
			expGTE bool
			expEQ  bool
		}{
			{pql.NewDecimal(0, 0), pql.NewDecimal(0, 0), false, true, false, true, true},
			{pql.NewDecimal(0, 0), pql.NewDecimal(10, 0), true, true, false, false, false},
			{pql.NewDecimal(10, 0), pql.NewDecimal(0, 0), false, false, true, true, false},
			{pql.NewDecimal(123456, 3), pql.NewDecimal(123456, 3), false, true, false, true, true},
			{pql.NewDecimal(123456, 3), pql.NewDecimal(123456, 4), false, false, true, true, false},
			{pql.NewDecimal(123456, 4), pql.NewDecimal(123456, 3), true, true, false, false, false},
			{pql.NewDecimal(1233456, 4), pql.NewDecimal(123456, 3), true, true, false, false, false},

			{pql.NewDecimal(0, 0), pql.NewDecimal(-10, 0), false, false, true, true, false},
			{pql.NewDecimal(-10, 0), pql.NewDecimal(0, 0), true, true, false, false, false},
			{pql.NewDecimal(-123456, 3), pql.NewDecimal(-123456, 3), false, true, false, true, true},
			{pql.NewDecimal(-123456, 3), pql.NewDecimal(-123456, 4), true, true, false, false, false},
			{pql.NewDecimal(-123456, 4), pql.NewDecimal(-123456, 3), false, false, true, true, false},
			{pql.NewDecimal(-1233456, 4), pql.NewDecimal(-123456, 3), false, false, true, true, false},

			{pql.NewDecimal(10, 0), pql.NewDecimal(-10, 0), false, false, true, true, false},
			{pql.NewDecimal(-10, 0), pql.NewDecimal(10, 0), true, true, false, false, false},
			{pql.NewDecimal(-123456, 3), pql.NewDecimal(123456, 3), true, true, false, false, false},
			{pql.NewDecimal(123456, 3), pql.NewDecimal(-123456, 3), false, false, true, true, false},
			{pql.NewDecimal(-123456, 3), pql.NewDecimal(123456, 4), true, true, false, false, false},
			{pql.NewDecimal(123456, 3), pql.NewDecimal(-123456, 4), false, false, true, true, false},
			{pql.NewDecimal(-123456, 4), pql.NewDecimal(123456, 3), true, true, false, false, false},
			{pql.NewDecimal(123456, 4), pql.NewDecimal(-123456, 3), false, false, true, true, false},
			{pql.NewDecimal(-1233456, 4), pql.NewDecimal(123456, 3), true, true, false, false, false},
			{pql.NewDecimal(1233456, 4), pql.NewDecimal(-123456, 3), false, false, true, true, false},

			{pql.NewDecimal(9223372036854775807, 0), pql.NewDecimal(9223372036854775807, 0), false, true, false, true, true},
			{pql.NewDecimal(9223372036854775807, 2), pql.NewDecimal(9223372036854775807, 0), true, true, false, false, false},
			{pql.NewDecimal(9223372036854775807, 19), pql.NewDecimal(9223372036854775807, 0), true, true, false, false, false},

			{pql.NewDecimal(-9223372036854775808, 0), pql.NewDecimal(-9223372036854775808, 0), false, true, false, true, true},
			{pql.NewDecimal(-9223372036854775808, 0), pql.NewDecimal(-9223372036854775807, 0), true, true, false, false, false},
			{pql.NewDecimal(-9223372036854775808, 2), pql.NewDecimal(-9223372036854775808, 0), false, false, true, true, false},
			{pql.NewDecimal(-9223372036854775808, 19), pql.NewDecimal(-9223372036854775807, 0), false, false, true, true, false},
		}
		for i, test := range tests {
			if got := test.d1.LessThan(test.d2); got != test.expLT {
				t.Fatalf("test LT %d expected %s < %s to be %v, but got: %v", i, test.d1, test.d2, test.expLT, got)
			}
			if got := test.d1.LessThanOrEqualTo(test.d2); got != test.expLTE {
				t.Fatalf("test LTE %d expected %s <= %s to be %v, but got: %v", i, test.d1, test.d2, test.expLTE, got)
			}
			if got := test.d1.GreaterThan(test.d2); got != test.expGT {
				t.Fatalf("test GT %d expected %s > %s to be %v, but got: %v", i, test.d1, test.d2, test.expGT, got)
			}
			if got := test.d1.GreaterThanOrEqualTo(test.d2); got != test.expGTE {
				t.Fatalf("test GTE %d expected %s >= %s to be %v, but got: %v", i, test.d1, test.d2, test.expGTE, got)
			}
			if got := test.d1.EqualTo(test.d2); got != test.expEQ {
				t.Fatalf("test EQ %d expected %s == %s to be %v, but got: %v", i, test.d1, test.d2, test.expEQ, got)
			}
		}
	})

	t.Run("JSON", func(t *testing.T) {
		t.Run("Unmarshal", func(t *testing.T) {
			tests := []struct {
				json string
				exp  pql.Decimal
			}{
				{"1234.56", pql.NewDecimal(123456, 2)},
			}
			for i, test := range tests {
				b := []byte(test.json)
				dec := &pql.Decimal{}
				if err := json.Unmarshal(b, &dec); err != nil {
					panic(err)
				}
				if !reflect.DeepEqual(*dec, test.exp) {
					t.Fatalf("test %d expected: %T, but got: %T", i, test.exp, dec)
				}
			}
		})
	})
}
