// Copyright 2013 by Dobrosław Żybort. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package slug

import (
	
	
	
	

	
)

var (
	// CustomSub stores custom substitution map
	CustomSub map[string]string
	// CustomRuneSub stores custom rune substitution map
	CustomRuneSub map[rune]string

	// MaxLength stores maximum slug length.
	// By default slugs aren't shortened.
	// If MaxLength is smaller than length of the first word, then returned
	// slug will contain only substring from the first word truncated
	// after MaxLength.
	MaxLength int

	// EnableSmartTruncate defines if cutting with MaxLength is smart.
	// Smart algorithm will cat slug after full word.
	// Default is true.
	EnableSmartTruncate = true

	// Lowercase defines if the resulting slug is transformed to lowercase.
	// Default is true.
	Lowercase = true

	regexpNonAuthorizedChars = regexp.MustCompile("[^a-zA-Z0-9-_]")
	regexpMultipleDashes     = regexp.MustCompile("-+")
)

//=============================================================================

// Make returns slug generated from provided string. Will use "en" as language
// substitution.
func ( string) ( string) {
	return MakeLang(, "en")
}

// MakeLang returns slug generated from provided string and will use provided
// language for chars substitution.
func ( string,  string) ( string) {
	 = strings.TrimSpace()

	// Custom substitutions
	// Always substitute runes first
	 = SubstituteRune(, CustomRuneSub)
	 = Substitute(, CustomSub)

	// Process string with selected substitution language.
	// Catch ISO 3166-1, ISO 639-1:2002 and ISO 639-3:2007.
	switch strings.ToLower() {
	case "bg", "bgr":
		 = SubstituteRune(, bgSub)
	case "cs", "ces":
		 = SubstituteRune(, csSub)
	case "de", "deu":
		 = SubstituteRune(, deSub)
	case "en", "eng":
		 = SubstituteRune(, enSub)
	case "es", "spa":
		 = SubstituteRune(, esSub)
	case "fi", "fin":
		 = SubstituteRune(, fiSub)
	case "fr", "fra":
		 = SubstituteRune(, frSub)
	case "gr", "el", "ell":
		 = SubstituteRune(, grSub)
	case "hu", "hun":
		 = SubstituteRune(, huSub)
	case "id", "idn", "ind":
		 = SubstituteRune(, idSub)
	case "it", "ita":
		 = SubstituteRune(, itSub)
	case "kz", "kk", "kaz":
		 = SubstituteRune(, kkSub)
	case "nb", "nob":
		 = SubstituteRune(, nbSub)
	case "nl", "nld":
		 = SubstituteRune(, nlSub)
	case "nn", "nno":
		 = SubstituteRune(, nnSub)
	case "pl", "pol":
		 = SubstituteRune(, plSub)
	case "ro", "rou":
		 = SubstituteRune(, roSub)
	case "sl", "slv":
		 = SubstituteRune(, slSub)
	case "sv", "swe":
		 = SubstituteRune(, svSub)
	case "tr", "tur":
		 = SubstituteRune(, trSub)
	default: // fallback to "en" if lang not found
		 = SubstituteRune(, enSub)
	}

	// Process all non ASCII symbols
	 = unidecode.Unidecode()

	if Lowercase {
		 = strings.ToLower()
	}

	if !EnableSmartTruncate && len() >= MaxLength {
		 = [:MaxLength]
	}

	// Process all remaining symbols
	 = regexpNonAuthorizedChars.ReplaceAllString(, "-")
	 = regexpMultipleDashes.ReplaceAllString(, "-")
	 = strings.Trim(, "-_")

	if MaxLength > 0 && EnableSmartTruncate {
		 = smartTruncate()
	}

	return 
}

// Substitute returns string with superseded all substrings from
// provided substitution map. Substitution map will be applied in alphabetic
// order. Many passes, on one substitution another one could apply.
func ( string,  map[string]string) ( string) {
	 = 
	var  []string
	for  := range  {
		 = append(, )
	}
	sort.Strings()

	for ,  := range  {
		 = strings.Replace(, , [], -1)
	}
	return
}

// SubstituteRune substitutes string chars with provided rune
// substitution map. One pass.
func ( string,  map[rune]string) string {
	var  bytes.Buffer
	for ,  := range  {
		if ,  := [];  {
			.WriteString()
		} else {
			.WriteRune()
		}
	}
	return .String()
}

func smartTruncate( string) string {
	if len() <= MaxLength {
		return 
	}

	// If slug is too long, we need to find the last '-' before MaxLength, and
	// we cut there.
	// If we don't find any, we have only one word, and we cut at MaxLength.
	for  := MaxLength;  >= 0; -- {
		if [] == '-' {
			return [:]
		}
	}
	return [:MaxLength]
}

// IsSlug returns True if provided text does not contain white characters,
// punctuation, all letters are lower case and only from ASCII range.
// It could contain `-` and `_` but not at the beginning or end of the text.
// It should be in range of the MaxLength var if specified.
// All output from slug.Make(text) should pass this test.
func ( string) bool {
	if  == "" ||
		(MaxLength > 0 && len() > MaxLength) ||
		[0] == '-' || [0] == '_' ||
		[len()-1] == '-' || [len()-1] == '_' {
		return false
	}
	for ,  := range  {
		if ( < 'a' ||  > 'z') &&  != '-' &&  != '_' && ( < '0' ||  > '9') {
			return false
		}
	}
	return true
}