package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"os/user"
	"runtime"
	"strings"
	"time"

	"github.com/akamensky/argparse"
)

var (
	marker          string = "## MANAGED BY HYPERFOCUS ##\n"
	distractorsFile string = "/etc/hyperfocus_distractors"
)

func getHostFile() string {

	var hostFile string
	switch runtime.GOOS {
	case "windows":
		hostFile = "/Windows/System32/drivers/etc/hosts"

	default:
		hostFile = "/etc/hosts"
	}

	return hostFile
}

func resetNetwork(message string) {

	switch runtime.GOOS {
	case "windows":
		cmd := exec.Command("ipconfig /flushdns")
		if err := cmd.Run(); err != nil {
			log.Fatalln(err)
		}

		fmt.Println(message)

	case "darwin":
		cmd := exec.Command("dscacheutil", "-flushcache")
		if err := cmd.Run(); err != nil {
			log.Fatalln(err)
		}

		fmt.Println(message)

	case "linux":
		var (
			usingDnsmasq  bool = false
			usingResolved bool = false
		)

		// Detect if using dnsmasq
		out, err := exec.Command("systemctl", "is-active", "dnsmasq").Output()
		if err != nil {
			log.Fatalln(err)
		}

		if string(out) == "active" {
			usingDnsmasq = true
		}

		// Detect if using systemd-resolved
		out, err = exec.Command("systemctl", "is-active", "systemd-resolved").Output()
		if err != nil {
			log.Fatalln(err)
		}

		if string(out) == "active" {
			usingResolved = true
		}

		// Restart networking service
		if usingDnsmasq {
			cmd := exec.Command("systemctl", "restart", "dnsmasq")
			if err := cmd.Run(); err != nil {
				log.Fatalln(err)
			}
		}

		if usingResolved {
			cmd := exec.Command("resolvectl", "flush-caches")
			if err := cmd.Run(); err != nil {
				log.Fatalln(err)
			}
		}

		fmt.Println(message)
	}
}

func isRoot() bool {

	currUser, err := user.Current()
	if err != nil {
		log.Fatalf("[Super user check] Unable to get current user: %s\n", err)
	}

	return currUser.Username == "root"
}

// Disables access to websites that are defined as 'distractors'
func improveFocus() {

	hostFile, err := os.OpenFile(getHostFile(), os.O_APPEND|os.O_WRONLY, 0666)
	if err != nil {
		log.Fatalln(err)
	}
	defer hostFile.Close()

	userDistractorsFile, err := os.Open(distractorsFile)
	if err != nil {
		log.Fatalln(err)
	}
	defer userDistractorsFile.Close()

	if _, err := hostFile.WriteString(marker); err != nil {
		log.Fatalln(err)
	}

	scanner := bufio.NewScanner(userDistractorsFile)
	for scanner.Scan() {
		var hostLine string = fmt.Sprintf("127.0.0.1\t%s\n", scanner.Text())
		if _, err := hostFile.WriteString(hostLine); err != nil {
			log.Fatalln(err)
		}
	}

	if _, err := hostFile.WriteString(marker); err != nil {
		log.Fatalln(err)
	}

	resetNetwork("Focus is now improved 😊")
}

// Enables access to websites that are defined as 'distractors'
func loseFocus() {

	hostFileContent, err := ioutil.ReadFile(getHostFile())
	if err != nil {
		log.Fatalln(err)
	}

	hostFileSlice := strings.Split(string(hostFileContent), marker)
	hostFileSlice = append(hostFileSlice[:1], hostFileSlice[2:]...)
	hostFileBytes := []byte(strings.Join(hostFileSlice, ""))

	if err := ioutil.WriteFile(getHostFile(), hostFileBytes, 0666); err != nil {
		log.Fatalln(err)
	}

	resetNetwork("Focus is now lost 🤪")
}

// Enables temporarily breaking concentration
func takeBreak(minutes int) {

	fmt.Println()
	fmt.Println("Your (probably) well-deserved break is commencing...")
	loseFocus()

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)

	sig := <-c
	t := minutes * 60
	for i := t; i > 0; i-- {
		fmt.Printf("%d seconds remaining", i)
		time.Sleep(time.Second)

		if sig == os.Interrupt {
			break
		}
	}

	improveFocus()
	fmt.Println("Welcome back to hyperfocus mode 😊 ")
}

// Prints the current list of distractors to be blocked
func listDistractors() {

	userDistractorsFile, err := os.Open(distractorsFile)
	if err != nil {
		log.Fatalln(err)
	}
	defer userDistractorsFile.Close()

	scanner := bufio.NewScanner(userDistractorsFile)

	for scanner.Scan() {
		fmt.Println(scanner.Text())
	}
}

// Adds predefined distractors to .hyperfocus_distractors file
func addPredefinedDistractors() {

	distractors := []string{
		"ycombinator.com",
		"slashdot.com",
		"facebook.com",
		"reddit.com",
		"gawker.com",
		"theverge.com",
		"techcrunch.com",
		"thenextweb.com",
		"wired.com",
		"gizmodo.com",
		"slickdeals.net",
		"mashable.com",
		"digitaltrends.com",
		"techradar.com",
		"twitter.com",
		"tumblr.com",
		"technorati.com",
		"digg.com",
		"buzzfeed.com",
		"twitter.com",
		"youtube.com",
		"netflix.com",
		"iwastesomuchtime.com",
		"pinterest.com",
		"ebay.com",
		"thepointsguy.com",
		"imgur.com",
		"woot.com",
		"flyertalk.com",
		"instagram.com",
		"medium.com",
		"meetup.com",
		"distrowatch.com",
		"arstechnica.com",
		"phoronix.com",
		"arstechnica.com",
		"failblog.com",
		"redfin.com",
		"realtor.com",
		"zillow.com",
		"trulia.com",
		"cnn.com",
		"fox.com",
		"realclearpolitics.com",
		"yelp.com",
		"opentable.com",
		"slashdot.org",
		"xkcd.com",
		"cnet.com",
		"tomshardware.com",
		"engadget.com",
		"zdnet.com",
		"techrepublic.com",
		"gizmag.com",
		"anandtech.com",
		"imore.com",
		"gsmarena.com ",
		"geek.com",
		"firstpost.com",
		"wearables.com",
		"stripgenerator.com",
		"fmylife.com",
		"liveplasma.com",
		"cracked.com",
		"befunky.com",
		"pcworld.com",
		"typepad.com",
		"pogo.com",
		"omegle.com",
		"lifehacker.com",
		"answerbag.com",
		"cheezburger.com",
		"fark.com",
		"popurls.com",
		"sho.com",
		"hulu.com",
		"myparentsjoinedfacebook.com",
		"homestarrunner.com",
		"petsinclothes.com",
		"freerice.com",
		"everypoet.com",
		"mono-1.com",
		"mcsweeneys.net",
		"postsecret.com",
		"textsfromlastnight.com",
		"awkwardfamilyphotos.com",
		"myspace.com",
		"lunchtimers.com",
		"twitterfall.com",
		"break.com",
		"passiveaggressivenotes.com",
		"sciencemag.org",
		"bbc.com",
		"notalwaysright.com",
	}

	userDistractorsFile, err := os.OpenFile(distractorsFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
	if err != nil {
		log.Fatalln(err)
	}
	defer userDistractorsFile.Close()

	dataWriter := bufio.NewWriter(userDistractorsFile)

	for _, v := range distractors {
		_, _ = dataWriter.WriteString(v + "\n")
	}
	dataWriter.Flush()
}

func main() {

	parser := argparse.NewParser("hf", "Block distracting websites and hyperfocus on your work")

	improveCmd := parser.NewCommand("improve", "Improve focus, block the distractors")
	loseCmd := parser.NewCommand("lose", "Lose focus, unblock the distractors")

	breakCmd := parser.NewCommand("break", "Take a break for a number of minutes")
	minutesForBreak := breakCmd.Int("", "minutes", &argparse.Options{Help: "Number of minutes to break for."})

	listCmd := parser.NewCommand("list", "List the distractors defined in the block file (/etc/hyperfocus_distractors)")
	predefinedCmd := parser.NewCommand("predefined", "Add predefined set of distractors to block file")

	if isRoot() {

		err := parser.Parse(os.Args)
		if err != nil {
			fmt.Print(parser.Usage(err))
		}

		if improveCmd.Happened() {
			improveFocus()
		} else if loseCmd.Happened() {
			loseFocus()
		} else if breakCmd.Happened() {
			takeBreak(*minutesForBreak)
		} else if listCmd.Happened() {
			listDistractors()
		} else if predefinedCmd.Happened() {
			addPredefinedDistractors()
		} else {
			fmt.Println("Enter a subcommand; use --help or -h for details.")
		}
	} else {
		fmt.Println("Super user not detected. This program requires root privileges; please re-run with sudo or become root.")
	}
}
