package cron

import (
	
	
	
	
	
)

// Configuration options for creating a parser. Most options specify which
// fields should be included, while others enable features. If a field is not
// included the parser will assume a default value. These options do not change
// the order fields are parse in.
type ParseOption int

const (
	Second         ParseOption = 1 << iota // Seconds field, default 0
	SecondOptional                         // Optional seconds field, default 0
	Minute                                 // Minutes field, default 0
	Hour                                   // Hours field, default 0
	Dom                                    // Day of month field, default *
	Month                                  // Month field, default *
	Dow                                    // Day of week field, default *
	DowOptional                            // Optional day of week field, default *
	Descriptor                             // Allow descriptors such as @monthly, @weekly, etc.
)

var places = []ParseOption{
	Second,
	Minute,
	Hour,
	Dom,
	Month,
	Dow,
}

var defaults = []string{
	"0",
	"0",
	"0",
	"*",
	"*",
	"*",
}

// A custom Parser that can be configured.
type Parser struct {
	options ParseOption
}

// NewParser creates a Parser with custom options.
//
// It panics if more than one Optional is given, since it would be impossible to
// correctly infer which optional is provided or missing in general.
//
// Examples
//
//  // Standard parser without descriptors
//  specParser := NewParser(Minute | Hour | Dom | Month | Dow)
//  sched, err := specParser.Parse("0 0 15 */3 *")
//
//  // Same as above, just excludes time fields
//  subsParser := NewParser(Dom | Month | Dow)
//  sched, err := specParser.Parse("15 */3 *")
//
//  // Same as above, just makes Dow optional
//  subsParser := NewParser(Dom | Month | DowOptional)
//  sched, err := specParser.Parse("15 */3")
//
func ( ParseOption) Parser {
	 := 0
	if &DowOptional > 0 {
		++
	}
	if &SecondOptional > 0 {
		++
	}
	if  > 1 {
		panic("multiple optionals may not be configured")
	}
	return Parser{}
}

// Parse returns a new crontab schedule representing the given spec.
// It returns a descriptive error if the spec is not valid.
// It accepts crontab specs and features configured by NewParser.
func ( Parser) ( string) (Schedule, error) {
	if len() == 0 {
		return nil, fmt.Errorf("empty spec string")
	}

	// Extract timezone if present
	var  = time.Local
	if strings.HasPrefix(, "TZ=") || strings.HasPrefix(, "CRON_TZ=") {
		var  error
		 := strings.Index(, " ")
		 := strings.Index(, "=")
		if ,  = time.LoadLocation([+1 : ]);  != nil {
			return nil, fmt.Errorf("provided bad location %s: %v", [+1:], )
		}
		 = strings.TrimSpace([:])
	}

	// Handle named schedules (descriptors), if configured
	if strings.HasPrefix(, "@") {
		if .options&Descriptor == 0 {
			return nil, fmt.Errorf("parser does not accept descriptors: %v", )
		}
		return parseDescriptor(, )
	}

	// Split on whitespace.
	 := strings.Fields()

	// Validate & fill in any omitted or optional fields
	var  error
	,  = normalizeFields(, .options)
	if  != nil {
		return nil, 
	}

	 := func( string,  bounds) uint64 {
		if  != nil {
			return 0
		}
		var  uint64
		,  = getField(, )
		return 
	}

	var (
		     = ([0], seconds)
		     = ([1], minutes)
		       = ([2], hours)
		 = ([3], dom)
		      = ([4], months)
		  = ([5], dow)
	)
	if  != nil {
		return nil, 
	}

	return &SpecSchedule{
		Second:   ,
		Minute:   ,
		Hour:     ,
		Dom:      ,
		Month:    ,
		Dow:      ,
		Location: ,
	}, nil
}

// normalizeFields takes a subset set of the time fields and returns the full set
// with defaults (zeroes) populated for unset fields.
//
// As part of performing this function, it also validates that the provided
// fields are compatible with the configured options.
func normalizeFields( []string,  ParseOption) ([]string, error) {
	// Validate optionals & add their field to options
	 := 0
	if &SecondOptional > 0 {
		 |= Second
		++
	}
	if &DowOptional > 0 {
		 |= Dow
		++
	}
	if  > 1 {
		return nil, fmt.Errorf("multiple optionals may not be configured")
	}

	// Figure out how many fields we need
	 := 0
	for ,  := range places {
		if & > 0 {
			++
		}
	}
	 :=  - 

	// Validate number of fields
	if  := len();  <  ||  >  {
		if  ==  {
			return nil, fmt.Errorf("expected exactly %d fields, found %d: %s", , , )
		}
		return nil, fmt.Errorf("expected %d to %d fields, found %d: %s", , , , )
	}

	// Populate the optional field if not provided
	if  <  && len() ==  {
		switch {
		case &DowOptional > 0:
			 = append(, defaults[5]) // TODO: improve access to default
		case &SecondOptional > 0:
			 = append([]string{defaults[0]}, ...)
		default:
			return nil, fmt.Errorf("unknown optional field")
		}
	}

	// Populate all fields not part of options with their defaults
	 := 0
	 := make([]string, len(places))
	copy(, defaults)
	for ,  := range places {
		if & > 0 {
			[] = []
			++
		}
	}
	return , nil
}

var standardParser = NewParser(
	Minute | Hour | Dom | Month | Dow | Descriptor,
)

// ParseStandard returns a new crontab schedule representing the given
// standardSpec (https://en.wikipedia.org/wiki/Cron). It requires 5 entries
// representing: minute, hour, day of month, month and day of week, in that
// order. It returns a descriptive error if the spec is not valid.
//
// It accepts
//   - Standard crontab specs, e.g. "* * * * ?"
//   - Descriptors, e.g. "@midnight", "@every 1h30m"
func ( string) (Schedule, error) {
	return standardParser.Parse()
}

// getField returns an Int with the bits set representing all of the times that
// the field represents or error parsing field value.  A "field" is a comma-separated
// list of "ranges".
func getField( string,  bounds) (uint64, error) {
	var  uint64
	 := strings.FieldsFunc(, func( rune) bool { return  == ',' })
	for ,  := range  {
		,  := getRange(, )
		if  != nil {
			return , 
		}
		 |= 
	}
	return , nil
}

// getRange returns the bits indicated by the given expression:
//   number | number "-" number [ "/" number ]
// or error parsing range.
func getRange( string,  bounds) (uint64, error) {
	var (
		, ,  uint
		     = strings.Split(, "/")
		       = strings.Split([0], "-")
		      = len() == 1
		              error
	)

	var  uint64
	if [0] == "*" || [0] == "?" {
		 = .min
		 = .max
		 = starBit
	} else {
		,  = parseIntOrName([0], .names)
		if  != nil {
			return 0, 
		}
		switch len() {
		case 1:
			 = 
		case 2:
			,  = parseIntOrName([1], .names)
			if  != nil {
				return 0, 
			}
		default:
			return 0, fmt.Errorf("too many hyphens: %s", )
		}
	}

	switch len() {
	case 1:
		 = 1
	case 2:
		,  = mustParseInt([1])
		if  != nil {
			return 0, 
		}

		// Special handling: "N/step" means "N-max/step".
		if  {
			 = .max
		}
		if  > 1 {
			 = 0
		}
	default:
		return 0, fmt.Errorf("too many slashes: %s", )
	}

	if  < .min {
		return 0, fmt.Errorf("beginning of range (%d) below minimum (%d): %s", , .min, )
	}
	if  > .max {
		return 0, fmt.Errorf("end of range (%d) above maximum (%d): %s", , .max, )
	}
	if  >  {
		return 0, fmt.Errorf("beginning of range (%d) beyond end of range (%d): %s", , , )
	}
	if  == 0 {
		return 0, fmt.Errorf("step of range should be a positive number: %s", )
	}

	return getBits(, , ) | , nil
}

// parseIntOrName returns the (possibly-named) integer contained in expr.
func parseIntOrName( string,  map[string]uint) (uint, error) {
	if  != nil {
		if ,  := [strings.ToLower()];  {
			return , nil
		}
	}
	return mustParseInt()
}

// mustParseInt parses the given expression as an int or returns an error.
func mustParseInt( string) (uint, error) {
	,  := strconv.Atoi()
	if  != nil {
		return 0, fmt.Errorf("failed to parse int from %s: %s", , )
	}
	if  < 0 {
		return 0, fmt.Errorf("negative number (%d) not allowed: %s", , )
	}

	return uint(), nil
}

// getBits sets all bits in the range [min, max], modulo the given step size.
func getBits(, ,  uint) uint64 {
	var  uint64

	// If step is 1, use shifts.
	if  == 1 {
		return ^(math.MaxUint64 << ( + 1)) & (math.MaxUint64 << )
	}

	// Else, use a simple loop.
	for  := ;  <= ;  +=  {
		 |= 1 << 
	}
	return 
}

// all returns all bits within the given bounds.  (plus the star bit)
func all( bounds) uint64 {
	return getBits(.min, .max, 1) | starBit
}

// parseDescriptor returns a predefined schedule for the expression, or error if none matches.
func parseDescriptor( string,  *time.Location) (Schedule, error) {
	switch  {
	case "@yearly", "@annually":
		return &SpecSchedule{
			Second:   1 << seconds.min,
			Minute:   1 << minutes.min,
			Hour:     1 << hours.min,
			Dom:      1 << dom.min,
			Month:    1 << months.min,
			Dow:      all(dow),
			Location: ,
		}, nil

	case "@monthly":
		return &SpecSchedule{
			Second:   1 << seconds.min,
			Minute:   1 << minutes.min,
			Hour:     1 << hours.min,
			Dom:      1 << dom.min,
			Month:    all(months),
			Dow:      all(dow),
			Location: ,
		}, nil

	case "@weekly":
		return &SpecSchedule{
			Second:   1 << seconds.min,
			Minute:   1 << minutes.min,
			Hour:     1 << hours.min,
			Dom:      all(dom),
			Month:    all(months),
			Dow:      1 << dow.min,
			Location: ,
		}, nil

	case "@daily", "@midnight":
		return &SpecSchedule{
			Second:   1 << seconds.min,
			Minute:   1 << minutes.min,
			Hour:     1 << hours.min,
			Dom:      all(dom),
			Month:    all(months),
			Dow:      all(dow),
			Location: ,
		}, nil

	case "@hourly":
		return &SpecSchedule{
			Second:   1 << seconds.min,
			Minute:   1 << minutes.min,
			Hour:     all(hours),
			Dom:      all(dom),
			Month:    all(months),
			Dow:      all(dow),
			Location: ,
		}, nil

	}

	const  = "@every "
	if strings.HasPrefix(, ) {
		,  := time.ParseDuration([len():])
		if  != nil {
			return nil, fmt.Errorf("failed to parse duration %s: %s", , )
		}
		return Every(), nil
	}

	return nil, fmt.Errorf("unrecognized descriptor: %s", )
}