// Copyright © 2017 Asteris, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"bytes"
	"fmt"
	"sort"

	"github.com/asteris-llc/converge/resource/systemd/unit"
	"github.com/coreos/go-systemd/dbus"
)

// unitTypeSlices - because sort.Sort requires that we implement sort.Interface
// to sort arbitary slices
type unitTypeSlice []unit.UnitType

// Len returns the length of the slice
func (u unitTypeSlice) Len() int {
	return len(u)
}

// Less returns true of i < j, and false otherwise
func (u unitTypeSlice) Less(i, j int) bool {
	return u[i] < u[j]
}

// Swap swaps the values at indexes i and j, in place
func (u unitTypeSlice) Swap(i, j int) {
	u[i] ^= u[j]
	u[j] ^= u[i]
	u[i] ^= u[j]
}

const copyrightNotice = `// Copyright © 2017 Asteris, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

`

const autoGeneratedNotice = `// Code generated by generate-dbus-wrappers
// DO NOT EDIT!
`

const packageName = "unit"

func main() {
	conn, err := dbus.New()
	if err != nil {
		fmt.Println("Error establishing dbus connection: ", err)
		return
	}
	u, err := conn.ListUnits()
	if err != nil {
		fmt.Println("Error getting units: ", err)
		return
	}

	unitMap := make(map[unit.UnitType][]dbus.UnitStatus)
	for _, st := range u {
		unitType := unit.UnitTypeFromName(st.Name)
		if unitType == unit.UnitTypeUnknown {
			continue
		}
		var lst []dbus.UnitStatus
		if oldLst, ok := unitMap[unitType]; ok {
			lst = oldLst
		} else {
			lst = make([]dbus.UnitStatus, 0)
		}
		unitMap[unitType] = append(lst, st)
	}

	fmt.Println(copyrightNotice)
	fmt.Println(autoGeneratedNotice)
	fmt.Printf("package %s\n", packageName)

	globalProperties, err := conn.GetUnitProperties(unitMap[unit.UnitTypeService][0].Name)
	if err != nil {
		fmt.Println("error getting global properties: ", err)
		return
	}

	fmt.Println(generateBindingsFromMap("Properties", globalProperties))

	unitSorted := unitTypeSlice{}
	for k := range unitMap {
		unitSorted = append(unitSorted, k)
	}
	sort.Sort(unitSorted)
	for _, u := range unitSorted {
		unit := unitMap[u][0]
		typeProps, err := conn.GetUnitTypeProperties(unit.Name, u.UnitTypeString())
		if err != nil {
			fmt.Println("error getting type properties for: ", unit.Name, u.UnitTypeString())
			return
		}
		structName := fmt.Sprintf("%sTypeProperties", u.UnitTypeString())
		properties := generateBindingsFromMap(structName, typeProps)
		fmt.Println(properties)
	}

}

func generateBindingsFromMap(structName string, m map[string]interface{}) string {
	funcDef := `
// new%sFromMap generates a new %s given a systemd property map
func new%sFromMap(m map[string]interface{}) *%s {
	s := &%s{}
`
	structDef := `
// %s wraps the dbus api for system with properties from this unit type
type %s struct {
`
	elemDef := "\t%s %T `export:\"%s\"`\n"
	assignDef := `
	if val, ok := m["%s"]; ok {
		s.%s = val.(%T)
	}
`

	var (
		sortedKeys  []string
		structLines bytes.Buffer
		funcLines   bytes.Buffer
	)

	for k := range m {
		sortedKeys = append(sortedKeys, k)
	}
	sort.Strings(sortedKeys)

	structLines.WriteString(fmt.Sprintf(structDef, structName, structName))
	funcLines.WriteString(fmt.Sprintf(funcDef, structName, structName, structName, structName, structName))

	for _, k := range sortedKeys {
		v := m[k]
		structLines.WriteString(fmt.Sprintf(elemDef, k, v, k))
		funcLines.WriteString(fmt.Sprintf(assignDef, k, k, v))
	}
	structLines.WriteString("}\n")
	funcLines.WriteString("\treturn s\n}\n")
	return structLines.String() + "\n" + funcLines.String()
}
