// MinifyJavaScript minifies the JavaScript using Google Closure Compiler and then proceed to attempt to provide a zopfli compressed version.
func MinifyJavaScript() error {
	var minifyError error

	if codeutilsShared.ExecutableExists("ccjs") { // If the ccjs executable exists
		fmt.Println("Minifying " + projectConfig.Name + " compiled JavaScript.")

		closureArgs := []string{ // Define closureArgs as
			"build/" + lowercaseProjectName + ".js",    // Yes, I like to compress things.
			"--compilation_level=SIMPLE_OPTIMIZATIONS", // Simple optimizations
			"--warning_level=QUIET",                    // Shush you...
		}

		closureOutput := codeutilsShared.ExecCommand("ccjs", closureArgs, false) // Run Google Closure Compiler and store the output in closureOutput

		codeutilsShared.WriteOrUpdateFile("build/"+lowercaseProjectName+".min.js", []byte(closureOutput), codeutilsShared.UniversalFileMode) // Write or update the minified JS file content to build/lowercaseProjectName.min.js

		if codeutilsShared.ExecutableExists("zopfli") { // If zopfli exists
			codeutilsShared.ExecCommand("zopfli", []string{"build/" + lowercaseProjectName + ".min.js"}, false) // Have zopfli run and gzip the contents
		}
	} else { // If ccjs does not exist
		minifyError = errors.New("ccjs" + executableNotInstalled)
	}

	return minifyError
}
// CompileLESS compiles individual branches of LESS (each branch having ability to pass custom options)
func CompileLESS() error {
	var compileError error

	if codeutilsShared.ExecutableExists("lessc") { // If the lessc executable exists
		if codeutilsShared.IsDir("src/less") { // If the src/less directory exists
			if len(projectConfig.LESS.Branches) == 0 { // If there aren't branches
				projectConfig.LESS.Branches["root"] = codeutilsShared.LESSBranchOptions{ // Add a "root" branch
					FileName: lowercaseProjectName + ".css", // Set Filename to lowercaseProjectName.css
					UseGlob:  true,
				}
			}

			for lessBranchName, lessBranchOptions := range projectConfig.LESS.Branches { // For each Go branch option in Branches
				fmt.Println("Compiling branch " + lessBranchName)
				LESSBranchCompiler(lessBranchOptions) // Compile the Branch
			}
		} else { // If the src/go directory does not exist
			compileError = errors.New("less" + dirDoesNotExistInSrc)
		}
	} else { // If lessc executable doesn't exist
		compileError = errors.New("lessc" + executableNotInstalled)
	}

	return compileError
}
// CompileGo analyzes the Go configuration and determine what binaries need to be compiled and from what source
func CompileGo() error {
	var compileError error

	if codeutilsShared.ExecutableExists("go") { // If the go executable exists
		if codeutilsShared.IsDir("src/go") || projectConfig.Go.CompileFromRoot { // If the src/go directory exists or we're compiling from root
			if len(projectConfig.Go.Branches) == 0 { // If there aren't branches
				projectConfig.Go.Branches["root"] = codeutilsShared.GoBranchOptions{ // Add a "root" branch
					BinaryName:       lowercaseProjectName, // Set BinaryName to lowercaseProjectName
					SourcesToInclude: []string{"src/go"},   // Compile all the things!
				}
			}

			for goBranchName, goBranchOptions := range projectConfig.Go.Branches { // For each Go branch option in Branches
				fmt.Println("Compiling branch " + goBranchName)
				GoBranchCompiler(goBranchOptions) // Compile the Branch
			}
		} else { // If the src/go directory does not exist
			compileError = errors.New("go " + dirDoesNotExistInSrc)
		}
	} else { // If go is not installed
		compileError = errors.New("go" + executableNotInstalled)
	}

	return compileError
}
// GetSystemPackageManager is responsible for returning the system's package manager, if any. Generally, this function is only called once, which is during bootstrap initialization.
func GetSystemPackageManager() string {
	var packageManager string // Define packageManager as the command / package manager used

	if codeutilsShared.ExecutableExists("apt-get") { // If we are using aptitude / apt-get (Debian or deriv.)
		packageManager = "apt-get"
	} else if codeutilsShared.ExecutableExists("eopkg") { // If we are using eopkg (Solus)
		packageManager = "eopkg"
	} else if codeutilsShared.ExecutableExists("dnf") { // If we are using dnf (Fedora,RHEL, deriv.)
		packageManager = "dnf"
	} else if codeutilsShared.ExecutableExists("pacman") { // If we are using pacman (Arch Linux, deriv.)
		packageManager = "pacman"
	} else if codeutilsShared.ExecutableExists("zypper") { // If we are using zypper (SUSE, openSUSE, deriv.)
		packageManager = "zypper"
	} else { // If we have no idea
		packageManager = "unknown"
	}

	return packageManager
}
// CompressHTML compresses the HTML files provided
func CompressHTML(directory string, files []string) error {
	var compressError error
	htmlMinifierFlags := []string{ // Oh god I am so sorry for all the flags
		"--case-sensitive",                    // Ensure case sensitivity for custom HTML tags
		"--collapse-whitespace",               // Collapse any whitespace
		"--remove-comments",                   // Remove comments
		"--remove-comments-from-cdata",        // Same as above but from CDATA
		"--remove-redundant-attributes",       // Fix your damn HTML, no need for redundancies
		"--remove-script-type-attributes",     // Remove script type attribute
		"--remove-style-link-type-attributes", // Remove style and link tag type attribute
	}

	if codeutilsShared.ExecutableExists("html-minifier") { // If the minifier exists
		if codeutilsShared.IsDir(directory) { // If the directory provided indeed exists
			if len(files) != 0 { // If there was files passed
				replacer := regexp.MustCompile(`"{{?\s?(type=")?\s(\w+)?\s(src="[^>]+)`) // Search for instances of messed up quoting due to html-minifier
				for _, file := range files {                                             // For each file in files
					fmt.Println("Compressing " + file + " in " + directory)

					file = directory + "/" + file                                    // Prepend the directory
					fileSpecificFlags := htmlMinifierFlags                           // Assign fileSpecificFlags flags as initially the same as htmlMinifierFlags
					fileSpecificFlags = append(fileSpecificFlags, []string{file}...) // Specify file to minify

					compressedFileContent := codeutilsShared.ExecCommand("html-minifier", fileSpecificFlags, false) // Run the html-minifier
					fixedContent := replacer.ReplaceAllString(compressedFileContent, `"{{ $1$2" $3 }}"`)            // $1 is type, $2 is the type value, $3 is src+lang

					codeutilsShared.WriteOrUpdateFile(file, []byte(fixedContent), 0755) // Update the file contents
				}
			} else { // If there was no files passed
				fmt.Println("No files provided.")
			}
		} else { // If the directory provided is not, in fact, a directory
			fmt.Println(directory + " is not a directory.")
		}
	} else { // If the minifier does not exist
		compressError = errors.New("html-minifier" + executableNotInstalled)
	}

	return compressError
}
// CompileTypeScript compiles Typescript into Javascript and ensure JavaScript is minified and optimized
func CompileTypeScript() error {
	var compileError error

	if codeutilsShared.ExecutableExists("tsc") { // If the tsc executable exists
		if codeutilsShared.IsDir("src/typescript") { // If src/typescript is a valid directory
			if !strings.HasPrefix(projectConfig.TypeScript.Target, "ES") { // If it either an empty string or does not begin with ES
				projectConfig.TypeScript.Target = "ES5" // Default to ES5
			}

			typescriptFileName, _ := codeutilsShared.FindClosestFile("src/typescript/" + lowercaseProjectName + ".ts") // Define typescriptFileName as the closest file name to the one we're providing
			baseFileName := strings.Replace(filepath.Base(typescriptFileName), ".ts", "", -1)                          // Set baseFileName to typescript file name but with .ts removed

			typescriptCompileFlags := []string{ // Define typescriptCompileFlags as the following options
				"--declaration",                              // Create a declaration file
				"--forceConsistentCasingInFileNames",         // Enforce consistency in file names
				"--noFallthroughCasesInSwitch",               // Disallow fallthrough cases in switches
				"--noImplicitReturns",                        // Disallow implicit returns
				"--outFile", "build/" + baseFileName + ".js", // Output a single JS file in the build dir
				"--removeComments",                          // Remove comments
				"--target", projectConfig.TypeScript.Target, // Set the target
				typescriptFileName, // Append the .ts name
			}

			commandOutput := codeutilsShared.ExecCommand("tsc", typescriptCompileFlags, false) // Call execCommand and get its commandOutput

			if !strings.Contains(commandOutput, "error TS") { // If tsc did not report any errors
				if projectConfig.TypeScript.MinifyContent { // If we should minify the content
					minifyFailure := MinifyJavaScript() // Call the minification func, set any error to minifyFailure

					if minifyFailure == nil { // If there was no error minifying
						minifyFileName := baseFileName + ".min.js"

						if projectConfig.TypeScript.UseLibreJSHeader && (projectConfig.TypeScript.LibreJSLicense != "") { // If we should use the LibreJSHeader and the license value is set
							var finalMinifiedContent string // Define finalMinifiedContent as the content we get from AddLicense

							finalMinifiedContent, compileError = librejsgopher.AddLicense(projectConfig.TypeScript.LibreJSLicense, "build/"+minifyFileName, true) // Add the requested license to the JS file, return content

							if (compileError == nil) && projectConfig.TypeScript.UniqueHash { // If there was no issue adding the license and we should be using a unique hash
								uniqueFileName := baseFileName + "-" + codeutilsShared.Sha512Sum(finalMinifiedContent, 1)[0:12] + ".min.js" // Append first 12 characters of hash
								os.Rename("build/"+minifyFileName, "build/"+uniqueFileName)                                                 // Move the file to one with a unique file name
								minifyFileName = uniqueFileName                                                                             // Change minifyFileName to uniqueName
							}
						}

						if projectConfig.UsesTests { // If we are using tests
							compileError = codeutilsShared.CopyFile("build/"+baseFileName+".js", "tests/design/js/"+baseFileName+".js") // Copy over the non-minified JS to the test js folder
							compileError = codeutilsShared.CopyFile("build/"+minifyFileName, "tests/design/js/"+minifyFileName)         // Copy over the minified JS from build to the test js folder
						}
					} else { // If there was an error minifying
						compileError = minifyFailure // Set compileError to the minifyFailure
					}
				}
			} else { // If tsc did report errors
				compileError = errors.New(commandOutput) // Set compileError to the commandOutput
			}
		} else { // If src/typescript is not a valid directory
			compileError = errors.New("typescript" + dirDoesNotExistInSrc)
		}
	} else { // If the typescript compiler is not installed on this system
		compileError = errors.New("tsc" + executableNotInstalled)
	}

	return compileError
}