// main.go: list a template's inputs, measure it and export it as a PDF with the PiQ SaaS API.
// Go 1.18 or later; the standard library only.
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

const template = "L=400,\"Length\"\nW=300,\"Width\"\nP1=[L,0]\nP2=[L,W]\nP3=[0,W]\nPOLYLINE @1,[P0,P1,P2,P3],TRUE,0"

var key, base = os.Getenv("PIQ_API_KEY"), strings.TrimSuffix(os.Getenv("PIQ_URL"), "/")

// Request is the JSON body of a request: a template (or its source), values and, to save, where to.
type Request struct {
	Template    string         `json:"template,omitempty"`
	Source      *Object        `json:"source,omitempty"`
	Values      map[string]any `json:"values,omitempty"`
	Destination *Object        `json:"destination,omitempty"`
	Overwrite   bool           `json:"overwrite,omitempty"`
}

// Object is an object in one of your registered storages.
type Object struct {
	Storage string `json:"storage"`
	Key     string `json:"key"`
}

func post(path string, body Request) *http.Response {
	data, _ := json.Marshal(body)
	req, _ := http.NewRequest("POST", base+path, bytes.NewReader(data))
	req.Header.Set("X-API-KEY", key)
	req.Header.Set("Content-Type", "application/json")
	response, err := http.DefaultClient.Do(req)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	if response.StatusCode >= 300 {
		fmt.Fprintln(os.Stderr, "Error", response.Status)
		var problem struct {
			Title, Detail string
			Errors        map[string][]string
		}
		text, _ := io.ReadAll(response.Body) // a 401 has no body
		if len(text) > 0 && json.Unmarshal(text, &problem) != nil {
			problem.Detail = string(text)
		}
		for _, line := range []string{problem.Title, problem.Detail} {
			if line != "" {
				fmt.Fprintln(os.Stderr, line)
			}
		}
		for field, messages := range problem.Errors {
			fmt.Fprintf(os.Stderr, "  %s: %s\n", field, strings.Join(messages, " "))
		}
		os.Exit(1)
	}
	return response
}

func postFor(path string, body Request, result any) {
	response := post(path, body)
	defer response.Body.Close()
	json.NewDecoder(response.Body).Decode(result)
}

func main() {
	if key == "" {
		fmt.Fprintln(os.Stderr, "Set PIQ_API_KEY to your API key.")
		os.Exit(1)
	}
	if base == "" {
		base = "https://saas.parametriq.co.uk"
	}
	storage := os.Getenv("PIQ_STORAGE") // optional: a registered storage's name
	// the template itself, or (optional) where it is kept in your storage
	request := Request{Template: template}
	if storage != "" {
		request = Request{Source: &Object{storage, "templates/box.piq"}}
	}

	// 1. The template's labelled variables
	var inputs struct {
		Inputs []struct {
			Name, Label, Kind string
			Value             any
		}
	}
	postFor("/inputs", request, &inputs)
	for _, i := range inputs.Inputs {
		fmt.Printf("%s: %s (%s, %v)\n", i.Name, i.Label, i.Kind, i.Value)
	}

	// 2. The drawing measured, with the values given
	var m struct {
		Width, Height float64
		Layers        []struct {
			Layer  string
			Length float64
		}
	}
	request.Values = map[string]any{"L": 500, "W": 250}
	postFor("/measure", request, &m)
	cut := 0.0
	for _, layer := range m.Layers {
		if layer.Layer == "Cut" {
			cut = layer.Length
		}
	}
	fmt.Printf("Size: %v x %v, cut length: %v\n", m.Width, m.Height, cut)

	// 3. The drawing as a PDF
	pdf := post("/export/pdf", request)
	data, _ := io.ReadAll(pdf.Body)
	if err := os.WriteFile("box.pdf", data, 0o644); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	fmt.Printf("Saved box.pdf (%s warnings)\n", pdf.Header.Get("X-PiQ-Warnings"))

	// 4. Optional: the PDF saved to your storage instead of downloaded
	if storage != "" {
		request.Destination, request.Overwrite = &Object{storage, "drawings/box.pdf"}, true
		var saved struct {
			Storage, Key string
			Size         int64
		}
		postFor("/save/pdf", request, &saved)
		fmt.Printf("Saved %s to %s (%d bytes)\n", saved.Key, saved.Storage, saved.Size)
	}
}
