func (s monitorSetIgnoringID) contains(x Monitor) (Monitor, bool) {
	withoutID := x
	withoutID.ID = nil
	str, _ := util.Marshal(withoutID, util.JSON)
	r, ok := s[str]
	return r, ok
}
func (monitor *Monitor) create(client *http.Client) error {
	repr, err := util.Marshal(monitor, util.JSON)
	if err != nil {
		return err
	}
	_, err = doHTTP(client, "POST",
		authedURL(monitorURL), repr)
	return err
}
func (monitor *Monitor) update(client *http.Client, target *Monitor) error {
	msg := target
	msg.ID = nil
	repr, err := util.Marshal(msg, util.JSON)
	if err != nil {
		return err
	}
	_, err = doHTTP(client, "PUT",
		authedURL(fmt.Sprintf("%s/%d", monitorURL, *monitor.ID)), repr)
	return err
}
func (s monitorSetIgnoringID) add(x Monitor) {
	withoutID := x
	withoutID.ID = nil
	str, _ := util.Marshal(withoutID, util.JSON)
	s[str] = x
}
func (s monitorSetIgnoringID) remove(x Monitor) {
	withoutID := x
	withoutID.ID = nil
	str, _ := util.Marshal(withoutID, util.JSON)
	delete(s, str)
}
// SyncMonitors creates Datadog metrics from local that aren't in Datadog,
// then update metrics in Datadog to metrics from local whose ID matches,
// then deletes metrics in Datadog that weren't accounted for in local
func SyncMonitors(local, remote []Monitor, client *http.Client, dryRun, verbose bool) error {
	remoteSetIgnoringID := make(monitorSetIgnoringID)
	remotesByID := make(map[id]Monitor)
	localSet := make(monitorSetIgnoringID)
	var toCreate []Monitor
	var toUpdate []update

	for _, r := range remote {
		remoteSetIgnoringID.add(r)
		remotesByID[*r.ID] = r
	}

	for _, l := range local {
		if l.ID == nil {
			if r, ok := remoteSetIgnoringID.contains(l); ok {
				delete(remotesByID, *r.ID)
			} else {
				toCreate = append(toCreate, l)
			}
		} else { // l has an ID
			if r, ok := remotesByID[*l.ID]; ok {
				delete(remotesByID, *r.ID)
				if !reflect.DeepEqual(l, r) {
					toUpdate = append(toUpdate, update{from: l, to: r})
				}
			} else {
				return fmt.Errorf("no remote alert #%d", *l.ID)
			}
		}
		localSet.add(l)
	}

	creations := len(toCreate)
	updates := len(toUpdate)
	deletions := len(remotesByID)
	total := creations + updates + deletions

	logrus.Infof("%d creations, %d updates, %d deletions", creations, updates, deletions)

	for i, m := range toCreate {
		logrus.Infof("CREATE %d/%d/%d: %s", i, creations, total, m.shortDescription())
		if !dryRun {
			if err := m.create(client); err != nil {
				return err
			}
		}
		if verbose {
			repr, _ := util.Marshal(m, util.YAML)
			logrus.Debug(repr)
		}
	}

	for i, u := range toUpdate {
		logrus.Infof("UPDATE %d/%d/%d: %s", i, updates, total, u.from.shortDescription())
		if !dryRun {
			if err := u.from.update(client, &u.to); err != nil {
				return err
			}
		}
		if verbose {
			f, _ := util.Marshal(u.from, util.YAML)
			t, _ := util.Marshal(u.to, util.YAML)
			logrus.Debugf("%s\n=>\n%s", f, t)
		}
	}

	idx := 0
	for _, m := range remotesByID {
		logrus.Infof("DELETE %d/%d/%d: %s", idx, deletions, total, m.shortDescription())
		idx++
		if !dryRun {
			if err := m.delete(client); err != nil {
				return err
			}
		}
		if verbose {
			repr, _ := util.Marshal(m, util.YAML)
			logrus.Debug(repr)
		}
	}

	return nil
}
func main() {
	logrus.SetLevel(logrus.DebugLevel)

	var action mode
	var format util.Format

	flag.Parse()

	switch *modeStr {
	case "pull":
		action = pull
	case "push":
		action = push
	default:
		logrus.Fatalf("unsupported mode %v", *modeStr)
	}

	switch *formatStr {
	case "json":
		format = util.JSON
	case "yaml":
		format = util.YAML
	default:
		logrus.Fatalf("unsupported format %v", *formatStr)
	}

	if *dd.APIKey == "" {
		var ok bool
		if *dd.APIKey, ok = os.LookupEnv("DATADOG_API_KEY"); !ok {
			logrus.Fatal("no API key provided")
		}
	}

	if *dd.AppKey == "" {
		var ok bool
		if *dd.AppKey, ok = os.LookupEnv("DATADOG_APP_KEY"); !ok {
			logrus.Fatal("no application key provided")
		}
	}

	client := &http.Client{}

	remote, err := dd.GetMonitors(client)
	if err != nil {
		logrus.WithError(err).Fatal("could not pull monitors")
	}

	remote, err = filteredMonitors(remote, *filter)
	if err != nil {
		logrus.WithError(err).Fatal("could not filter remote monitors")
	}

	switch action {
	case pull:
		var repr string
		if !*withIds {
			for i := range remote {
				remote[i].ID = nil
			}
		}
		repr, err = util.Marshal(remote, format)
		if err != nil {
			logrus.WithError(err).Fatal("could not serialize monitors")
		}
		fmt.Println(repr)
	case push:
		var local []dd.Monitor
		repr, err := ioutil.ReadAll(os.Stdin)
		if err != nil {
			logrus.WithError(err).Fatal("could not read from standard input")
		}

		if err = util.Unmarshal(repr, &local, format); err != nil {
			logrus.WithError(err).Fatal("could not deserialize monitors")
		}

		local, err = filteredMonitors(local, *filter)
		if err != nil {
			logrus.WithError(err).Fatal("could not filter local monitors")
		}

		if err = dd.SyncMonitors(local, remote, client, *dryRun, *verbose); err != nil {
			logrus.WithError(err).Fatal("could not sync monitors: %v")
		}
	}
}