package main

import (
	"fmt"
	"time"
)

type Employee struct {
	ID        int
	FirstName string
	LastName  string
	DateHired time.Time
}

type Manager struct {
	Employee
	Reports []int
}

type SimpleEmployeeData struct {
	employees map[int]Employee
	managers  map[int]Manager
	nextID    int
}

func NewSimpleEmployeeData() *SimpleEmployeeData {
	return &SimpleEmployeeData{
		employees: map[int]Employee{},
		managers:  map[int]Manager{},
		nextID:    0,
	}
}

func (ed *SimpleEmployeeData) AddEmployee(firstName, lastName string, dateHired time.Time) int {
	ed.nextID++
	ed.employees[ed.nextID] = Employee{
		ID:        ed.nextID,
		FirstName: firstName,
		LastName:  lastName,
		DateHired: dateHired,
	}
	return ed.nextID
}

func (ed SimpleEmployeeData) GetEmployee(id int) (Employee, bool) {
	p, ok := ed.employees[id]
	return p, ok
}

func (ed *SimpleEmployeeData) AddManager(firstName, lastName string, dateHired time.Time, reports []int) int {
	ed.nextID++
	ed.managers[ed.nextID] = Manager{
		Employee: Employee{
			ID:        ed.nextID,
			FirstName: firstName,
			LastName:  lastName,
			DateHired: dateHired,
		},
		Reports: reports,
	}
	return ed.nextID
}

func (ed SimpleEmployeeData) GetManager(id int) (Manager, bool) {
	m, ok := ed.managers[id]
	return m, ok
}

func DMYToTime(day int, month time.Month, year int) time.Time {
	return time.Date(year, month, day, 0, 0, 0, 0, time.UTC)
}

func main() {
	ed := NewSimpleEmployeeData()
	manageEmployees(ed)
}

func manageEmployees(ed EmployeeData) {
	e1ID := ed.AddEmployee("Bob", "Bobson", DMYToTime(10, time.January, 2020))
	e2ID := ed.AddEmployee("Mary", "Maryson", DMYToTime(30, time.March, 2007))
	e1, exists1 := ed.GetEmployee(e1ID)
	e2, exists2 := ed.GetEmployee(e2ID)
	fmt.Println(e1, exists1)
	fmt.Println(e2, exists2)
	e3, exists3 := ed.GetEmployee(2000)
	fmt.Println(e3, exists3)

	m1ID := ed.AddManager("Boss", "BossPerson", DMYToTime(17, time.June, 1982), []int{e1ID, e2ID})
	m1, _ := ed.GetManager(m1ID)
	fmt.Println(m1.FirstName, m1.Reports)

	var e4 = m1.Employee
	fmt.Println(e4.LastName)

	fmt.Println(FormatTenure(e1))
	fmt.Println(FormatTenure(e2))

	fmt.Println(m1.TimeAtCompany())

	fmt.Println(FormatTenure(m1))
	fmt.Println(m1)
}

type Dater interface {
	TimeAtCompany() time.Duration
}

func FormatTenure(d Dater) string {
	// convert from hours to years and days (ignoring leap years)
	hours := int(d.TimeAtCompany().Hours())
	years := hours / (24 * 365)
	hours = hours % (24 * 365)
	days := hours / 24
	return fmt.Sprintf("%d years, %d days", years, days)
}

func (e Employee) TimeAtCompany() time.Duration {
	return time.Since(e.DateHired)
}

func (e Employee) String() string {
	return e.FirstName + " " + e.LastName + ": Tenure " + FormatTenure(e)
}

func (m Manager) String() string {
	return fmt.Sprintf("%s, reports: %v", m.Employee, m.Reports)
}

type EmployeeData interface {
	AddEmployee(firstName, lastName string, dateHired time.Time) int
	GetEmployee(id int) (Employee, bool)
	AddManager(firstName, lastName string, dateHired time.Time, reports []int) int
	GetManager(id int) (Manager, bool)
}
