package main

import (
	"bufio"
	"errors"
	"fmt"
	"os"
	"strings"
)

type Animal interface {
	Eat()
	Move()
	Speak()
}

type Cow struct {
	name       string
	food       string `default:"grass"`
	locomotion string `default:"walk"`
	sound      string `default:"moo"`
}

func (a Cow) Eat() {
	fmt.Println(a.food)
}

func (a Cow) Move() {
	fmt.Println(a.locomotion)
}
func (a Cow) Speak() {
	fmt.Println(a.sound)
}

type Bird struct {
	Name       string
	Food       string `default:"worms"`
	Locomotion string `default:"fly"`
	Sound      string `default:"peep"`
}

func (a Bird) Eat() {
	fmt.Println(a.Food)
}

func (a Bird) Move() {
	fmt.Println(a.Locomotion)
}
func (a Bird) Speak() {
	fmt.Println(a.Sound)
}

type Snake struct {
	Name       string
	Food       string `default:"mice"`
	Locomotion string `default:"slither"`
	Sound      string `default:"hsss"`
}

func (a Snake) Eat() {
	fmt.Println(a.Food)
}

func (a Snake) Move() {
	fmt.Println(a.Locomotion)
}
func (a Snake) Speak() {
	fmt.Println(a.Sound)
}

func NewAnimal(name string, animalType string) (Animal, error) {
	switch animalType {
	case "cow":
		return Cow{name, "grass", "walk", "moo"}, nil
	case "bird":
		return Bird{name, "worms", "fly", "peep"}, nil
	case "snake":
		return Snake{name, "mice", "slither", "hiss"}, nil
	default:
		return Cow{}, errors.New(fmt.Sprintf("Error! Failed to create animal of type: %s. Options {cow, bird, snake}.", animalType))
	}
}

func queryAnimal(animal Animal, query string) {
	switch query {
	case "eat":
		animal.Eat()
	case "move":
		animal.Move()
	case "speak":
		animal.Speak()
	default:
		fmt.Printf("Error! Query parameter: %s invalid. Options: {eat, move, speak}\n", query)
	}
}

// Use the empty interface to map string to Any type
// var animalMap map[string]interface{}
var animalMap map[string]Animal

func main() {
	animalMap = make(map[string]Animal)
	scanner := bufio.NewScanner(os.Stdin)

	fmt.Println("\n\nWelcome to your personal animal database. Two commands are available {newanimal, query}\n")
	fmt.Println("The \"newanimal\" command  takes two parameters (name, animalType) seperated by white space.")
	fmt.Println("\"newanimal\" animal types are restricted to {cow, bird, snake}")
	fmt.Println("Example: \"newanimal bob cow\"\n")
	fmt.Println("The \"query\" command takes two parameters (name, action) seperated by white space.")
	fmt.Println("\"query\" actions are restricted to {eat, move, speak}")
	fmt.Println("Example: \"query bob eat\"\n")

	for {
		fmt.Print(">")
		scanner.Scan()
		input := scanner.Text()
		input = strings.TrimSpace(input)
		splitInput := strings.Fields(input)

		// Ensure user command is proper size
		if len(splitInput) != 3 {
			fmt.Println("Error! Invalid input parameters. Please try again.")
			continue
		}

		command := splitInput[0]
		animalName := splitInput[1]
		arg := splitInput[2]

		switch command {
		case "newanimal":
			userAnimal, err := NewAnimal(animalName, arg)
			if err != nil {
				fmt.Println(err)
				continue
			}
			animalMap[animalName] = userAnimal
		case "query":
			// check animal is in datastore
			val, present := animalMap[animalName]
			if present {
				queryAnimal(val, arg)
			} else {
				fmt.Printf("Error! Failed to find animal %s in database\n", animalName)
			}
		default:
			fmt.Println("Error! Unrecognized user command. Options {newanimal, query}")
		}
	}

}