package pgtype

import (
	
	
	
	
	
	
	

	
)

type DateScanner interface {
	ScanDate(v Date) error
}

type DateValuer interface {
	DateValue() (Date, error)
}

type Date struct {
	Time             time.Time
	InfinityModifier InfinityModifier
	Valid            bool
}

func ( *Date) ( Date) error {
	* = 
	return nil
}

func ( Date) () (Date, error) {
	return , nil
}

const (
	negativeInfinityDayOffset = -2147483648
	infinityDayOffset         = 2147483647
)

// Scan implements the database/sql Scanner interface.
func ( *Date) ( any) error {
	if  == nil {
		* = Date{}
		return nil
	}

	switch src := .(type) {
	case string:
		return scanPlanTextAnyToDateScanner{}.Scan([]byte(), )
	case time.Time:
		* = Date{Time: , Valid: true}
		return nil
	}

	return fmt.Errorf("cannot scan %T", )
}

// Value implements the database/sql/driver Valuer interface.
func ( Date) () (driver.Value, error) {
	if !.Valid {
		return nil, nil
	}

	if .InfinityModifier != Finite {
		return .InfinityModifier.String(), nil
	}
	return .Time, nil
}

func ( Date) () ([]byte, error) {
	if !.Valid {
		return []byte("null"), nil
	}

	var  string

	switch .InfinityModifier {
	case Finite:
		 = .Time.Format("2006-01-02")
	case Infinity:
		 = "infinity"
	case NegativeInfinity:
		 = "-infinity"
	}

	return json.Marshal()
}

func ( *Date) ( []byte) error {
	var  *string
	 := json.Unmarshal(, &)
	if  != nil {
		return 
	}

	if  == nil {
		* = Date{}
		return nil
	}

	switch * {
	case "infinity":
		* = Date{Valid: true, InfinityModifier: Infinity}
	case "-infinity":
		* = Date{Valid: true, InfinityModifier: -Infinity}
	default:
		,  := time.ParseInLocation("2006-01-02", *, time.UTC)
		if  != nil {
			return 
		}

		* = Date{Time: , Valid: true}
	}

	return nil
}

type DateCodec struct{}

func (DateCodec) ( int16) bool {
	return  == TextFormatCode ||  == BinaryFormatCode
}

func (DateCodec) () int16 {
	return BinaryFormatCode
}

func (DateCodec) ( *Map,  uint32,  int16,  any) EncodePlan {
	if ,  := .(DateValuer); ! {
		return nil
	}

	switch  {
	case BinaryFormatCode:
		return encodePlanDateCodecBinary{}
	case TextFormatCode:
		return encodePlanDateCodecText{}
	}

	return nil
}

type encodePlanDateCodecBinary struct{}

func (encodePlanDateCodecBinary) ( any,  []byte) ( []byte,  error) {
	,  := .(DateValuer).DateValue()
	if  != nil {
		return nil, 
	}

	if !.Valid {
		return nil, nil
	}

	var  int32
	switch .InfinityModifier {
	case Finite:
		 := time.Date(.Time.Year(), .Time.Month(), .Time.Day(), 0, 0, 0, 0, time.UTC).Unix()
		 := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).Unix()

		 :=  - 
		 = int32( / 86400)
	case Infinity:
		 = infinityDayOffset
	case NegativeInfinity:
		 = negativeInfinityDayOffset
	}

	return pgio.AppendInt32(, ), nil
}

type encodePlanDateCodecText struct{}

func (encodePlanDateCodecText) ( any,  []byte) ( []byte,  error) {
	,  := .(DateValuer).DateValue()
	if  != nil {
		return nil, 
	}

	if !.Valid {
		return nil, nil
	}

	switch .InfinityModifier {
	case Finite:
		// Year 0000 is 1 BC
		 := false
		 := .Time.Year()
		if  <= 0 {
			 = - + 1
			 = true
		}

		 := strconv.AppendInt(make([]byte, 0, 6), int64(), 10)
		for  := len();  < 4; ++ {
			 = append(, '0')
		}
		 = append(, ...)
		 = append(, '-')
		if .Time.Month() < 10 {
			 = append(, '0')
		}
		 = strconv.AppendInt(, int64(.Time.Month()), 10)
		 = append(, '-')
		if .Time.Day() < 10 {
			 = append(, '0')
		}
		 = strconv.AppendInt(, int64(.Time.Day()), 10)

		if  {
			 = append(, " BC"...)
		}
	case Infinity:
		 = append(, "infinity"...)
	case NegativeInfinity:
		 = append(, "-infinity"...)
	}

	return , nil
}

func (DateCodec) ( *Map,  uint32,  int16,  any) ScanPlan {

	switch  {
	case BinaryFormatCode:
		switch .(type) {
		case DateScanner:
			return scanPlanBinaryDateToDateScanner{}
		}
	case TextFormatCode:
		switch .(type) {
		case DateScanner:
			return scanPlanTextAnyToDateScanner{}
		}
	}

	return nil
}

type scanPlanBinaryDateToDateScanner struct{}

func (scanPlanBinaryDateToDateScanner) ( []byte,  any) error {
	 := ().(DateScanner)

	if  == nil {
		return .ScanDate(Date{})
	}

	if len() != 4 {
		return fmt.Errorf("invalid length for date: %v", len())
	}

	 := int32(binary.BigEndian.Uint32())

	switch  {
	case infinityDayOffset:
		return .ScanDate(Date{InfinityModifier: Infinity, Valid: true})
	case negativeInfinityDayOffset:
		return .ScanDate(Date{InfinityModifier: -Infinity, Valid: true})
	default:
		 := time.Date(2000, 1, int(1+), 0, 0, 0, 0, time.UTC)
		return .ScanDate(Date{Time: , Valid: true})
	}
}

type scanPlanTextAnyToDateScanner struct{}

var dateRegexp = regexp.MustCompile(`^(\d{4,})-(\d\d)-(\d\d)( BC)?$`)

func (scanPlanTextAnyToDateScanner) ( []byte,  any) error {
	 := ().(DateScanner)

	if  == nil {
		return .ScanDate(Date{})
	}

	 := string()
	 := dateRegexp.FindStringSubmatch()
	if  != nil {
		,  := strconv.ParseInt([1], 10, 32)
		if  != nil {
			return fmt.Errorf("BUG: cannot parse date that regexp matched (year): %v", )
		}

		,  := strconv.ParseInt([2], 10, 32)
		if  != nil {
			return fmt.Errorf("BUG: cannot parse date that regexp matched (month): %v", )
		}

		,  := strconv.ParseInt([3], 10, 32)
		if  != nil {
			return fmt.Errorf("BUG: cannot parse date that regexp matched (month): %v", )
		}

		// BC matched
		if len([4]) > 0 {
			 = - + 1
		}

		 := time.Date(int(), time.Month(), int(), 0, 0, 0, 0, time.UTC)
		return .ScanDate(Date{Time: , Valid: true})
	}

	switch  {
	case "infinity":
		return .ScanDate(Date{InfinityModifier: Infinity, Valid: true})
	case "-infinity":
		return .ScanDate(Date{InfinityModifier: -Infinity, Valid: true})
	default:
		return fmt.Errorf("invalid date format")
	}
}

func ( DateCodec) ( *Map,  uint32,  int16,  []byte) (driver.Value, error) {
	if  == nil {
		return nil, nil
	}

	var  Date
	 := codecScan(, , , , , &)
	if  != nil {
		return nil, 
	}

	if .InfinityModifier != Finite {
		return .InfinityModifier.String(), nil
	}

	return .Time, nil
}

func ( DateCodec) ( *Map,  uint32,  int16,  []byte) (any, error) {
	if  == nil {
		return nil, nil
	}

	var  Date
	 := codecScan(, , , , , &)
	if  != nil {
		return nil, 
	}

	if .InfinityModifier != Finite {
		return .InfinityModifier, nil
	}

	return .Time, nil
}