/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 utils

import (
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"

	"github.com/apache/openwhisk-wskdeploy/wskprint"
)

// check if the path represents file path or dir path
func isFilePath(path string) bool {
	// when split returns dir and file, splitting path on the final "/"
	// check if file is not empty to classify that path as a file path
	_, file := filepath.Split(path)
	if len(file) == 0 {
		return false
	}
	return true
}

// check if the given path exists as a file
func isFile(path string) (bool, error) {
	var err error
	var info os.FileInfo
	// run stat on the file and if the it returns no error,
	// read the fileInfo to check if its a file or not
	if info, err = os.Stat(path); err == nil {
		if info.Mode().IsRegular() {
			return true, nil
		}
	}
	// stat returned an error and here we are checking if it was os.PathError
	if !os.IsNotExist(err) {
		return false, nil
	}
	// after running through all the possible checks, return false and an err
	return false, err
}

// copy one single source file to the destination path
func copyFile(src, dst string) error {
	var err error
	var sourceFD *os.File
	var destFD *os.File
	var srcInfo os.FileInfo
	var srcDirInfo os.FileInfo

	wskprint.PrintlnOpenWhiskVerbose(Flags.Verbose, "Source File ["+src+"] is being copied to ["+dst+"]")

	// open source file to read it from disk and defer to close the file
	// once this function is done executing
	if sourceFD, err = os.Open(src); err != nil {
		return err
	}
	defer sourceFD.Close()

	// running stat on Dir(src), Dir returns all but the last element of the src
	// this info is needed in case when a destination path has a directory structure which does not exist
	if srcDirInfo, err = os.Stat(filepath.Dir(src)); err != nil {
		return err
	}

	// check if the parent directory exist before creating a destination file
	// create specified path along with creating any parent directory
	// e.g. when destination is greeting/common/utils.js and parent dir common
	// doesn't exist, its getting created here at greeting/common
	if _, err = os.Stat(filepath.Dir(dst)); os.IsNotExist(err) {
		wskprint.PrintlnOpenWhiskVerbose(Flags.Verbose, "Creating directory pattern ["+filepath.Dir(dst)+"] before creating destination file")
		if err = os.MkdirAll(filepath.Dir(dst), srcDirInfo.Mode()); err != nil {
			return err
		}
	}

	// create destination file before copying source content
	// defer closing the destination file until the function is done executing
	if destFD, err = os.Create(dst); err != nil {
		return err
	}
	defer destFD.Close()

	// now, actually copy the source file content into destination file
	if _, err = io.Copy(destFD, sourceFD); err != nil {
		return err
	}

	// retrieve the file mode bits of the source file
	// so that the bits can be set to the destination file
	if srcInfo, err = os.Stat(src); err != nil {
		return err
	}
	return os.Chmod(dst, srcInfo.Mode())
}

// recursively copy the entire source directory to destination path
func copyDir(src, dst string) error {
	var err error
	var fileDescriptors []os.FileInfo
	var srcInfo os.FileInfo

	// retrieve os.fileInfo of the source directory
	if srcInfo, err = os.Stat(src); err != nil {
		return err
	}

	// create destination directory with parent directories
	if err = os.MkdirAll(dst, srcInfo.Mode()); err != nil {
		return err
	}

	// now, retrieve all the directory/file entries under the source directory
	if fileDescriptors, err = ioutil.ReadDir(src); err != nil {
		return err
	}

	// iterating over the entire list of files/directories under the destination path
	// run copyFile or recursive copyDir based on if its file or dir
	for _, fd := range fileDescriptors {
		srcFilePath := path.Join(src, fd.Name())
		dstFilePath := path.Join(dst, fd.Name())

		if fd.IsDir() {
			if err = copyDir(srcFilePath, dstFilePath); err != nil {
				return err
			}
		} else {
			if err = copyFile(srcFilePath, dstFilePath); err != nil {
				return err
			}
		}
	}
	return nil
}
