func main() {
	port := os.Getenv("PORT")

	if port == "" {
		log.Fatal("$PORT must be set")
	}

	db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
	if err != nil {
		log.Fatalf("Error opening db: %q", err)
	}

	router := gin.Default()
	router.Use(gin.Logger())

	router.LoadHTMLGlob("templates/*.tmpl.html")
	router.Static("/static", "static")

	router.GET("/", func(c *gin.Context) {
		decFactors := m.DefaultDecimationFactors()
		c.HTML(http.StatusOK, "index.tmpl.html", gin.H{
			"title":       "Spectrum Viewer",
			"dec_factors": decFactors,
		})
	})

	router.GET("/survey", func(c *gin.Context) {
		surveyHandler(c, db)
	})

	router.GET("/sample", func(c *gin.Context) {
		sampleHandler(c, db)
	})

	router.GET("/longsample", func(c *gin.Context) {
		longSampleHandler(c, db)
	})

	authorized := router.Group("/priv")
	authorized.Use(TokenAuthMiddleware(db))
	authorized.POST("/upload", func(c *gin.Context) {
		uploadHandler(c, db)
	})

	router.Run(":" + port)
}
func uploadHandler(c *gin.Context, db *sql.DB) {
	type IncomingUpload struct {
		Survey  m.Survey         `json:"survey"`
		Samples []m.SampleVector `json:"samples"`
	}

	var file multipart.File

	file, _, err := c.Request.FormFile("file")
	if err != nil {
		c.String(http.StatusBadRequest, "Bad request: %q", err)
		return
	}

	var incoming IncomingUpload
	buf := new(bytes.Buffer)
	buf.ReadFrom(file)

	if err := json.Unmarshal(buf.Bytes(), &incoming); err != nil {
		c.String(http.StatusBadRequest, "Invalid JSON: %q", err)
		return
	}

	incoming.Survey.RawData = string(buf.Bytes())
	if uploaderId, err := strconv.Atoi(c.Request.Header["X-Uploader-Id"][0]); err != nil {
		c.String(http.StatusInternalServerError, "Missing header: X-Uploader-Id")
		return
	} else {
		incoming.Survey.UploaderId = uploaderId
	}

	insertDone := make(chan error)

	go func(db *sql.DB, inc *IncomingUpload, insertDone chan error) {
		var tx *sql.Tx
		tx, err := db.Begin()

		rollbackAndExit := func(err error) {
			if tx != nil {
				tx.Rollback()
			}
			log.Printf("DB Error: %q", err)
			insertDone <- err
		}

		if err != nil {
			rollbackAndExit(err)
			return
		}

		var surveyID int
		surveyID, err = inc.Survey.WriteToDB(tx)
		if err != nil {
			rollbackAndExit(err)
			return
		}

		var buffer bytes.Buffer
		preamble := `INSERT INTO sample (power, freq, bandwidth, decfactor, survey_id) VALUES `
		batchSize := 500

		// Decimate the samples with all default decimation factors
		for _, df := range m.DefaultDecimationFactors() {
			log.Printf("DF = %d", df)
			// Decimation
			var dSamples []m.Sample
			rawLen := len(inc.Samples)
			nBins := int(math.Ceil(float64(rawLen) / float64(df)))
			for i := 0; i < int(nBins); i++ {
				endIndex := int(df) * (i + 1)
				if endIndex > rawLen {
					endIndex = rawLen
				}
				binSamples := inc.Samples[int(df)*i : endIndex]
				sum := 0.0
				for _, sample := range binSamples {
					sum += sample[0] // Power
				}
				dSamples = append(dSamples, m.Sample{
					sum / float64(len(binSamples)),
					uint64((binSamples[0][1] + binSamples[len(binSamples)-1][1]) / 2),
					uint32(binSamples[0][2]) +
						uint32(binSamples[len(binSamples)-1][1]-binSamples[0][1]),
				})
			}

			// Save to DB
			for i, sample := range dSamples {
				batchI := i % batchSize
				if batchI == 0 {
					// Beginning of batch
					buffer.Reset()
					buffer.WriteString(preamble)
				}

				buffer.WriteString(
					fmt.Sprintf("(%f, %d, %d, %d, %d)",
						sample.Power, sample.Freq, sample.Bandwidth, df, surveyID))

				if batchI == batchSize-1 || i == len(dSamples)-1 {
					// End of batch
					if _, err := tx.Exec(buffer.String()); err != nil {
						rollbackAndExit(err)
						return
					}
				} else {
					buffer.WriteRune(',')
				}
			}
		}

		if err := tx.Commit(); err != nil {
			rollbackAndExit(err)
			return
		}

		insertDone <- nil
	}(db, &incoming, insertDone)

	if insertErr := <-insertDone; insertErr != nil {
		c.String(http.StatusInternalServerError, "DB Error: %q", insertErr)
	} else {
		c.String(http.StatusOK, "OK")
	}
}