package cascadia

import (
	
	
	
	

	
	
)

// This file implements the pseudo classes selectors,
// which share the implementation of PseudoElement() and Specificity()

type abstractPseudoClass struct{}

func ( abstractPseudoClass) () Specificity {
	return Specificity{0, 1, 0}
}

func ( abstractPseudoClass) () string {
	return ""
}

type relativePseudoClassSelector struct {
	name  string // one of "not", "has", "haschild"
	match SelectorGroup
}

func ( relativePseudoClassSelector) ( *html.Node) bool {
	if .Type != html.ElementNode {
		return false
	}
	switch .name {
	case "not":
		// matches elements that do not match a.
		return !.match.Match()
	case "has":
		//  matches elements with any descendant that matches a.
		return hasDescendantMatch(, .match)
	case "haschild":
		// matches elements with a child that matches a.
		return hasChildMatch(, .match)
	default:
		panic(fmt.Sprintf("unsupported relative pseudo class selector : %s", .name))
	}
}

// hasChildMatch returns whether n has any child that matches a.
func hasChildMatch( *html.Node,  Matcher) bool {
	for  := .FirstChild;  != nil;  = .NextSibling {
		if .Match() {
			return true
		}
	}
	return false
}

// hasDescendantMatch performs a depth-first search of n's descendants,
// testing whether any of them match a. It returns true as soon as a match is
// found, or false if no match is found.
func hasDescendantMatch( *html.Node,  Matcher) bool {
	for  := .FirstChild;  != nil;  = .NextSibling {
		if .Match() || (.Type == html.ElementNode && (, )) {
			return true
		}
	}
	return false
}

// Specificity returns the specificity of the most specific selectors
// in the pseudo-class arguments.
// See https://www.w3.org/TR/selectors/#specificity-rules
func ( relativePseudoClassSelector) () Specificity {
	var  Specificity
	for ,  := range .match {
		 := .Specificity()
		if .Less() {
			 = 
		}
	}
	return 
}

func ( relativePseudoClassSelector) () string {
	return ""
}

type containsPseudoClassSelector struct {
	abstractPseudoClass
	value string
	own   bool
}

func ( containsPseudoClassSelector) ( *html.Node) bool {
	var  string
	if .own {
		// matches nodes that directly contain the given text
		 = strings.ToLower(nodeOwnText())
	} else {
		// matches nodes that contain the given text.
		 = strings.ToLower(nodeText())
	}
	return strings.Contains(, .value)
}

type regexpPseudoClassSelector struct {
	abstractPseudoClass
	regexp *regexp.Regexp
	own    bool
}

func ( regexpPseudoClassSelector) ( *html.Node) bool {
	var  string
	if .own {
		// matches nodes whose text directly matches the specified regular expression
		 = nodeOwnText()
	} else {
		// matches nodes whose text matches the specified regular expression
		 = nodeText()
	}
	return .regexp.MatchString()
}

// writeNodeText writes the text contained in n and its descendants to b.
func writeNodeText( *html.Node,  *bytes.Buffer) {
	switch .Type {
	case html.TextNode:
		.WriteString(.Data)
	case html.ElementNode:
		for  := .FirstChild;  != nil;  = .NextSibling {
			(, )
		}
	}
}

// nodeText returns the text contained in n and its descendants.
func nodeText( *html.Node) string {
	var  bytes.Buffer
	writeNodeText(, &)
	return .String()
}

// nodeOwnText returns the contents of the text nodes that are direct
// children of n.
func nodeOwnText( *html.Node) string {
	var  bytes.Buffer
	for  := .FirstChild;  != nil;  = .NextSibling {
		if .Type == html.TextNode {
			.WriteString(.Data)
		}
	}
	return .String()
}

type nthPseudoClassSelector struct {
	abstractPseudoClass
	a, b         int
	last, ofType bool
}

func ( nthPseudoClassSelector) ( *html.Node) bool {
	if .a == 0 {
		if .last {
			return simpleNthLastChildMatch(.b, .ofType, )
		} else {
			return simpleNthChildMatch(.b, .ofType, )
		}
	}
	return nthChildMatch(.a, .b, .last, .ofType, )
}

// nthChildMatch implements :nth-child(an+b).
// If last is true, implements :nth-last-child instead.
// If ofType is true, implements :nth-of-type instead.
func nthChildMatch(,  int, ,  bool,  *html.Node) bool {
	if .Type != html.ElementNode {
		return false
	}

	 := .Parent
	if  == nil {
		return false
	}

	 := -1
	 := 0
	for  := .FirstChild;  != nil;  = .NextSibling {
		if (.Type != html.ElementNode) || ( && .Data != .Data) {
			continue
		}
		++
		if  ==  {
			 = 
			if ! {
				break
			}
		}
	}

	if  == -1 {
		// This shouldn't happen, since n should always be one of its parent's children.
		return false
	}

	if  {
		 =  -  + 1
	}

	 -= 
	if  == 0 {
		return  == 0
	}

	return % == 0 && / >= 0
}

// simpleNthChildMatch implements :nth-child(b).
// If ofType is true, implements :nth-of-type instead.
func simpleNthChildMatch( int,  bool,  *html.Node) bool {
	if .Type != html.ElementNode {
		return false
	}

	 := .Parent
	if  == nil {
		return false
	}

	 := 0
	for  := .FirstChild;  != nil;  = .NextSibling {
		if .Type != html.ElementNode || ( && .Data != .Data) {
			continue
		}
		++
		if  ==  {
			return  == 
		}
		if  >=  {
			return false
		}
	}
	return false
}

// simpleNthLastChildMatch implements :nth-last-child(b).
// If ofType is true, implements :nth-last-of-type instead.
func simpleNthLastChildMatch( int,  bool,  *html.Node) bool {
	if .Type != html.ElementNode {
		return false
	}

	 := .Parent
	if  == nil {
		return false
	}

	 := 0
	for  := .LastChild;  != nil;  = .PrevSibling {
		if .Type != html.ElementNode || ( && .Data != .Data) {
			continue
		}
		++
		if  ==  {
			return  == 
		}
		if  >=  {
			return false
		}
	}
	return false
}

type onlyChildPseudoClassSelector struct {
	abstractPseudoClass
	ofType bool
}

// Match implements :only-child.
// If `ofType` is true, it implements :only-of-type instead.
func ( onlyChildPseudoClassSelector) ( *html.Node) bool {
	if .Type != html.ElementNode {
		return false
	}

	 := .Parent
	if  == nil {
		return false
	}

	 := 0
	for  := .FirstChild;  != nil;  = .NextSibling {
		if (.Type != html.ElementNode) || (.ofType && .Data != .Data) {
			continue
		}
		++
		if  > 1 {
			return false
		}
	}

	return  == 1
}

type inputPseudoClassSelector struct {
	abstractPseudoClass
}

// Matches input, select, textarea and button elements.
func ( inputPseudoClassSelector) ( *html.Node) bool {
	return .Type == html.ElementNode && (.Data == "input" || .Data == "select" || .Data == "textarea" || .Data == "button")
}

type emptyElementPseudoClassSelector struct {
	abstractPseudoClass
}

// Matches empty elements.
func ( emptyElementPseudoClassSelector) ( *html.Node) bool {
	if .Type != html.ElementNode {
		return false
	}

	for  := .FirstChild;  != nil;  = .NextSibling {
		switch .Type {
		case html.ElementNode:
			return false
		case html.TextNode:
			if strings.TrimSpace(nodeText()) == "" {
				continue
			} else {
				return false
			}
		}
	}

	return true
}

type rootPseudoClassSelector struct {
	abstractPseudoClass
}

// Match implements :root
func ( rootPseudoClassSelector) ( *html.Node) bool {
	if .Type != html.ElementNode {
		return false
	}
	if .Parent == nil {
		return false
	}
	return .Parent.Type == html.DocumentNode
}

func hasAttr( *html.Node,  string) bool {
	return matchAttribute(, , func(string) bool { return true })
}

type linkPseudoClassSelector struct {
	abstractPseudoClass
}

// Match implements :link
func ( linkPseudoClassSelector) ( *html.Node) bool {
	return (.DataAtom == atom.A || .DataAtom == atom.Area || .DataAtom == atom.Link) && hasAttr(, "href")
}

type langPseudoClassSelector struct {
	abstractPseudoClass
	lang string
}

func ( langPseudoClassSelector) ( *html.Node) bool {
	 := matchAttribute(, "lang", func( string) bool {
		return  == .lang || strings.HasPrefix(, .lang+"-")
	})
	if .Parent == nil {
		return 
	}
	return  || .(.Parent)
}

type enabledPseudoClassSelector struct {
	abstractPseudoClass
}

func ( enabledPseudoClassSelector) ( *html.Node) bool {
	if .Type != html.ElementNode {
		return false
	}
	switch .DataAtom {
	case atom.A, atom.Area, atom.Link:
		return hasAttr(, "href")
	case atom.Optgroup, atom.Menuitem, atom.Fieldset:
		return !hasAttr(, "disabled")
	case atom.Button, atom.Input, atom.Select, atom.Textarea, atom.Option:
		return !hasAttr(, "disabled") && !inDisabledFieldset()
	}
	return false
}

type disabledPseudoClassSelector struct {
	abstractPseudoClass
}

func ( disabledPseudoClassSelector) ( *html.Node) bool {
	if .Type != html.ElementNode {
		return false
	}
	switch .DataAtom {
	case atom.Optgroup, atom.Menuitem, atom.Fieldset:
		return hasAttr(, "disabled")
	case atom.Button, atom.Input, atom.Select, atom.Textarea, atom.Option:
		return hasAttr(, "disabled") || inDisabledFieldset()
	}
	return false
}

func hasLegendInPreviousSiblings( *html.Node) bool {
	for  := .PrevSibling;  != nil;  = .PrevSibling {
		if .DataAtom == atom.Legend {
			return true
		}
	}
	return false
}

func inDisabledFieldset( *html.Node) bool {
	if .Parent == nil {
		return false
	}
	if .Parent.DataAtom == atom.Fieldset && hasAttr(.Parent, "disabled") &&
		(.DataAtom != atom.Legend || hasLegendInPreviousSiblings()) {
		return true
	}
	return (.Parent)
}

type checkedPseudoClassSelector struct {
	abstractPseudoClass
}

func ( checkedPseudoClassSelector) ( *html.Node) bool {
	if .Type != html.ElementNode {
		return false
	}
	switch .DataAtom {
	case atom.Input, atom.Menuitem:
		return hasAttr(, "checked") && matchAttribute(, "type", func( string) bool {
			 := toLowerASCII()
			return  == "checkbox" ||  == "radio"
		})
	case atom.Option:
		return hasAttr(, "selected")
	}
	return false
}