// Copyright 2017 The Bazel Authors. All rights reserved.
//
// 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.

// This binary accepts a CSV file with columns:
// 1. Name- Name of a software package.
// 2. Version- Version string for the software package.
// and transforms the CSV into a YAML of the following format:
// packages:
// - name: package1
//   version: version1
// - name: package2
//   version: version2
// ...
//
// The entires in the "packages" field in the YAML will be sorted by the
// package names.
package main

import (
	"encoding/csv"
	"flag"
	"fmt"
	"github.com/pkg/errors"
	"gopkg.in/yaml.v2"
	"io"
	"io/ioutil"
	"log"
	"os"
	"sort"
	"strings"

	"github.com/bazelbuild/rules_docker/contrib/go/pkg/metadata"
)

var (
	inputCSV   = flag.String("inputCSV", "", "Path to the metadata csv file generated by the download_pkgs rule in rules_docker")
	outputYAML = flag.String("outputYAML", "", "Path to the output YAML file to generate.")
)

// newPackageMetadataFromCSV reads the package metadata from the given
// CSV file with columns "Name" & "Version" and returns the corresponding
// PackagesMetadata object. The package entries in the returned PackagesMetadata
// are sorted by their package names.
func newPackagesMetadataFromCSV(csvFile string) (*metadata.PackagesMetadata, error) {
	result := new(metadata.PackagesMetadata)
	f, err := os.Open(csvFile)
	if err != nil {
		return nil, errors.Wrapf(err, "unable to open %s for reading", csvFile)
	}
	r := csv.NewReader(f)
	header, err := r.Read()
	if err != nil {
		return nil, errors.Wrapf(err, "unable to read CSV header from %s", csvFile)
	}
	if len(header) != 2 || header[0] != "Name" || header[1] != "Version" {
		return nil, fmt.Errorf("unexpected CSV header in %s, got %s, want Name,Version", csvFile, strings.Join(header, ","))
	}
	for {
		record, err := r.Read()
		if err == io.EOF {
			break
		}
		if err != nil {
			return nil, errors.Wrapf(err, "error parsing record in CSV file %s", csvFile)
		}
		result.Packages = append(result.Packages, metadata.PackageMetadata{
			Name:    record[0],
			Version: record[1],
		})
	}
	// Sort the packages in alphabetical order of their names.
	sort.Slice(result.Packages, func(i, j int) bool {
		pi, pj := result.Packages[i], result.Packages[j]
		return strings.Compare(pi.Name, pj.Name) < 0
	})
	return result, nil
}

func main() {
	flag.Parse()
	log.Println("Running the package metadata CSV to YAML converter.")
	log.Printf("-inputCSV %q", *inputCSV)
	log.Printf("-outputYAML %q", *outputYAML)
	if *inputCSV == "" {
		log.Fatalf("-inputCSV was not specified.")
	}
	if *outputYAML == "" {
		log.Fatalf("-outputYAML was not specified.")
	}

	p, err := newPackagesMetadataFromCSV(*inputCSV)
	if err != nil {
		log.Fatalf("Unable to load package metadata from %s: %v", *inputCSV, err)
	}
	log.Printf("Loaded %d records from %s.", len(p.Packages), *inputCSV)
	yamlBlob, err := yaml.Marshal(p)
	if err != nil {
		log.Fatalf("Unable to convert packaged metadata loaded from %s into YAML: %v", *inputCSV, err)
	}
	if err := ioutil.WriteFile(*outputYAML, yamlBlob, os.FileMode(0644)); err != nil {
		log.Fatalf("Unable to write out package metadata YAML to %s: %v", *outputYAML, err)
	}
	log.Printf("Wrote the packages metadata to %s.", *outputYAML)
}
