// Copyright 2018 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.

// Package nativelib creates the native library zip.
package nativelib

import (
	"archive/zip"
	"bufio"
	"flag"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"sort"
	"sync"

	"src/common/golang/fileutils"
	"src/common/golang/flags"
	"src/common/golang/ziputils"
	"src/tools/ak/types"
)

var (
	// Cmd defines the command to run nativelib.
	Cmd = types.Command{
		Init:  Init,
		Run:   Run,
		Desc:  desc,
		Flags: []string{"lib", "native_libs_zip", "out"},
	}

	// Variables to hold flag values
	architecture  string
	nativeLibs    flags.StringList
	nativeLibsZip flags.StringList
	out           string

	initOnce sync.Once
)

// Init initializes nativelib.
func Init() {
	initOnce.Do(func() {
		flag.StringVar(&architecture, "architecture", "", "CPU architecture of the native libs.")
		flag.Var(&nativeLibs, "lib", "Path to native lib.")
		flag.Var(&nativeLibsZip, "native_libs_zip", "Zip(s) containing native libs.")
		flag.StringVar(&out, "out", "", "Native libraries files.")
	})
}

func desc() string {
	return "Nativelib creates the native lib zip."
}

// Run is the entry point for nativelib.
func Run() {
	if nativeLibsZip != nil {
		dstDir, err := ioutil.TempDir("", "ziplibs")
		if err != nil {
			log.Fatalf("Error creating native lib zip: %v", err)
		}

		for _, native := range nativeLibsZip {
			libs, err := extractLibs(native, dstDir)
			if err != nil {
				log.Fatalf("Error creating native lib zip: %v", err)
			}
			nativeLibs = append(nativeLibs, libs...)
		}
	}

	if err := doWork(nativeLibs, architecture, out); err != nil {
		log.Fatalf("Error creating native lib zip: %v", err)
	}
}

func extractLibs(libZip, dstDir string) ([]string, error) {
	zr, err := zip.OpenReader(libZip)
	if err != nil {
		return nil, err
	}
	defer zr.Close()

	libs := []string{}
	for _, f := range zr.File {
		if f.Mode().IsDir() {
			continue
		}
		libs = append(libs, filepath.Join(dstDir, f.Name))
	}
	if err := ziputils.Unzip(libZip, dstDir); err != nil {
		return nil, err
	}
	return libs, nil
}

func doWork(nativeLibs []string, architecture, out string) error {
	nativeDir, err := ioutil.TempDir("", "nativelib")
	if err != nil {
		return err
	}
	defer os.RemoveAll(nativeDir)
	nativePaths, err := copyNativeLibs(nativeLibs, architecture, nativeDir)
	if err != nil {
		return err
	}
	zipFile, err := os.Create(out)
	if err != nil {
		return err
	}
	writer := bufio.NewWriter(zipFile)
	zipWriter := zip.NewWriter(writer)
	sort.Strings(nativePaths)
	for _, f := range nativePaths {
		p, err := filepath.Rel(nativeDir, f)
		if err != nil {
			return err
		}
		ziputils.WriteFile(zipWriter, f, p)
	}
	zipWriter.Close()
	return nil
}

func copyNativeLibs(nativeLibs []string, architecture, dir string) ([]string, error) {
	var paths []string
	for _, nativeLib := range nativeLibs {
		libOutDir := filepath.Join(dir, "lib", architecture)
		if err := os.MkdirAll(libOutDir, 0777); err != nil && !os.IsExist(err) {
			return nil, err
		}
		outNativeLibPath := filepath.Join(libOutDir, filepath.Base(nativeLib))
		if err := fileutils.Copy(nativeLib, outNativeLibPath); err != nil {
			return nil, err
		}
		paths = append(paths, outNativeLibPath)
	}
	return paths, nil
}
