package godotenv

import (
	
	
	
	
	
	
)

const (
	charComment       = '#'
	prefixSingleQuote = '\''
	prefixDoubleQuote = '"'

	exportPrefix = "export"
)

func parseBytes( []byte,  map[string]string) error {
	 = bytes.Replace(, []byte("\r\n"), []byte("\n"), -1)
	 := 
	for {
		 = getStatementStart()
		if  == nil {
			// reached end of file
			break
		}

		, ,  := locateKeyName()
		if  != nil {
			return 
		}

		, ,  := extractVarValue(, )
		if  != nil {
			return 
		}

		[] = 
		 = 
	}

	return nil
}

// getStatementPosition returns position of statement begin.
//
// It skips any comment line or non-whitespace character.
func getStatementStart( []byte) []byte {
	 := indexOfNonSpaceChar()
	if  == -1 {
		return nil
	}

	 = [:]
	if [0] != charComment {
		return 
	}

	// skip comment section
	 = bytes.IndexFunc(, isCharFunc('\n'))
	if  == -1 {
		return nil
	}

	return ([:])
}

// locateKeyName locates and parses key name and returns rest of slice
func locateKeyName( []byte) ( string,  []byte,  error) {
	// trim "export" and space at beginning
	 = bytes.TrimLeftFunc(, isSpace)
	if bytes.HasPrefix(, []byte(exportPrefix)) {
		 := bytes.TrimPrefix(, []byte(exportPrefix))
		if bytes.IndexFunc(, isSpace) == 0 {
			 = bytes.TrimLeftFunc(, isSpace)
		}
	}

	// locate key name end and validate it in single loop
	 := 0
:
	for ,  := range  {
		 := rune()
		if isSpace() {
			continue
		}

		switch  {
		case '=', ':':
			// library also supports yaml-style value declaration
			 = string([0:])
			 =  + 1
			break 
		case '_':
		default:
			// variable name should match [A-Za-z0-9_.]
			if unicode.IsLetter() || unicode.IsNumber() ||  == '.' {
				continue
			}

			return "", nil, fmt.Errorf(
				`unexpected character %q in variable name near %q`,
				string(), string())
		}
	}

	if len() == 0 {
		return "", nil, errors.New("zero length string")
	}

	// trim whitespace
	 = strings.TrimRightFunc(, unicode.IsSpace)
	 = bytes.TrimLeftFunc([:], isSpace)
	return , , nil
}

// extractVarValue extracts variable value and returns rest of slice
func extractVarValue( []byte,  map[string]string) ( string,  []byte,  error) {
	,  := hasQuotePrefix()
	if ! {
		// unquoted value - read until end of line
		 := bytes.IndexFunc(, isLineEnd)

		// Hit EOF without a trailing newline
		if  == -1 {
			 = len()

			if  == 0 {
				return "", nil, nil
			}
		}

		// Convert line to rune away to do accurate countback of runes
		 := []rune(string([0:]))

		// Assume end of line is end of var
		 := len()
		if  == 0 {
			return "", [:], nil
		}

		// Work backwards to check if the line ends in whitespace then
		// a comment (ie asdasd # some comment)
		for  :=  - 1;  >= 0; -- {
			if [] == charComment &&  > 0 {
				if isSpace([-1]) {
					 = 
					break
				}
			}
		}

		 := strings.TrimFunc(string([0:]), isSpace)

		return expandVariables(, ), [:], nil
	}

	// lookup quoted string terminator
	for  := 1;  < len(); ++ {
		if  := [];  !=  {
			continue
		}

		// skip escaped quote symbol (\" or \', depends on quote)
		if  := [-1];  == '\\' {
			continue
		}

		// trim quotes
		 := isCharFunc(rune())
		 = string(bytes.TrimLeftFunc(bytes.TrimRightFunc([0:], ), ))
		if  == prefixDoubleQuote {
			// unescape newlines for double quote (this is compat feature)
			// and expand environment variables
			 = expandVariables(expandEscapes(), )
		}

		return , [+1:], nil
	}

	// return formatted error if quoted string is not terminated
	 := bytes.IndexFunc(, isCharFunc('\n'))
	if  == -1 {
		 = len()
	}

	return "", nil, fmt.Errorf("unterminated quoted value %s", [:])
}

func expandEscapes( string) string {
	 := escapeRegex.ReplaceAllStringFunc(, func( string) string {
		 := strings.TrimPrefix(, `\`)
		switch  {
		case "n":
			return "\n"
		case "r":
			return "\r"
		default:
			return 
		}
	})
	return unescapeCharsRegex.ReplaceAllString(, "$1")
}

func indexOfNonSpaceChar( []byte) int {
	return bytes.IndexFunc(, func( rune) bool {
		return !unicode.IsSpace()
	})
}

// hasQuotePrefix reports whether charset starts with single or double quote and returns quote character
func hasQuotePrefix( []byte) ( byte,  bool) {
	if len() == 0 {
		return 0, false
	}

	switch  := [0];  {
	case prefixDoubleQuote, prefixSingleQuote:
		return , true
	default:
		return 0, false
	}
}

func isCharFunc( rune) func(rune) bool {
	return func( rune) bool {
		return  == 
	}
}

// isSpace reports whether the rune is a space character but not line break character
//
// this differs from unicode.IsSpace, which also applies line break as space
func isSpace( rune) bool {
	switch  {
	case '\t', '\v', '\f', '\r', ' ', 0x85, 0xA0:
		return true
	}
	return false
}

func isLineEnd( rune) bool {
	if  == '\n' ||  == '\r' {
		return true
	}
	return false
}

var (
	escapeRegex        = regexp.MustCompile(`\\.`)
	expandVarRegex     = regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`)
	unescapeCharsRegex = regexp.MustCompile(`\\([^$])`)
)

func expandVariables( string,  map[string]string) string {
	return expandVarRegex.ReplaceAllStringFunc(, func( string) string {
		 := expandVarRegex.FindStringSubmatch()

		if  == nil {
			return 
		}
		if [1] == "\\" || [2] == "(" {
			return [0][1:]
		} else if [4] != "" {
			return [[4]]
		}
		return 
	})
}