/*
 * Copyright 2017 Baidu, Inc.
 *
 * 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.
 */

// string.go - define the string util function

// Package util defines the common utilities including string and time.
package util

import (
	"bytes"
	"crypto/hmac"
	"crypto/md5"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"hash/crc32"
	"io"
	"os"
	"strconv"
)

func HmacSha256Hex(key, str_to_sign string) string {
	hasher := hmac.New(sha256.New, []byte(key))
	hasher.Write([]byte(str_to_sign))
	return hex.EncodeToString(hasher.Sum(nil))
}

func CalculateContentMD5(data io.Reader, size int64) (string, error) {
	hasher := md5.New()
	n, err := io.CopyN(hasher, data, size)
	if err != nil {
		return "", fmt.Errorf("calculate content-md5 occurs error: %v", err)
	}
	if n != size {
		return "", fmt.Errorf("calculate content-md5 writing size %d != size %d", n, size)
	}
	return base64.StdEncoding.EncodeToString(hasher.Sum(nil)), nil
}

func CalculateContentCrc32c(data io.Reader, size int64) (string, error) {
	castagnoliTable := crc32.MakeTable(crc32.Castagnoli)
	hashCrc32c := crc32.New(castagnoliTable)
	n, err := io.CopyN(hashCrc32c, data, size)
	if err != nil {
		return "", fmt.Errorf("calculate content-crc32c occurs error: %w", err)
	}
	if n != size {
		return "", fmt.Errorf("calculate content-crc32c writing size %d != size %d", n, size)
	}
	return strconv.FormatUint(uint64(hashCrc32c.Sum32()), 10), nil
}

func CalculateContentCrc32cFromStream(reader io.Reader) (string, error) {
	var buf bytes.Buffer
	rdLen, err := io.Copy(&buf, reader)
	if err != nil {
		return "", err
	}
	if rdLen != int64(buf.Len()) {
		return "", fmt.Errorf("unexpected reader")
	}
	return CalculateContentCrc32c(bytes.NewBuffer(buf.Bytes()), rdLen)
}

func CalculateContentCrc32cFromFile(fileName string) (string, error) {
	file, err := os.Open(fileName)
	if err != nil {
		return "", err
	}
	defer file.Close()
	fileInfo, infoErr := file.Stat()
	if infoErr != nil {
		return "", infoErr
	}
	return CalculateContentCrc32c(file, fileInfo.Size())
}

func CalculateContentCrc32(data io.Reader, size int64) (string, error) {
	hashCrc32 := crc32.NewIEEE()
	n, err := io.CopyN(hashCrc32, data, size)
	if err != nil {
		return "", fmt.Errorf("calculate content-crc32 occurs error: %w", err)
	}
	if n != size {
		return "", fmt.Errorf("calculate content-crc32 writing size %d != size %d", n, size)
	}
	return strconv.FormatUint(uint64(hashCrc32.Sum32()), 10), nil
}

func CalculateContentCrc32FromFile(file *os.File, offset, size int64) (string, error) {
	curOffset, err := file.Seek(0, io.SeekCurrent)
	if err != nil {
		return "", err
	}
	defer func() {
		file.Seek(curOffset, io.SeekStart)
	}()
	if _, err := file.Seek(offset, io.SeekStart); err != nil {
		return "", err
	}
	return CalculateContentCrc32(file, size)
}

func UriEncode(uri string, encodeSlash bool) string {
	var byte_buf bytes.Buffer
	for _, b := range []byte(uri) {
		if (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') || (b >= '0' && b <= '9') ||
			b == '-' || b == '_' || b == '.' || b == '~' || (b == '/' && !encodeSlash) {
			byte_buf.WriteByte(b)
		} else {
			byte_buf.WriteString(fmt.Sprintf("%%%02X", b))
		}
	}
	return byte_buf.String()
}

func NewUUID() string {
	var buf [16]byte
	for {
		if _, err := rand.Read(buf[:]); err == nil {
			break
		}
	}
	buf[6] = (buf[6] & 0x0f) | (4 << 4)
	buf[8] = (buf[8] & 0xbf) | 0x80

	res := make([]byte, 36)
	hex.Encode(res[0:8], buf[0:4])
	res[8] = '-'
	hex.Encode(res[9:13], buf[4:6])
	res[13] = '-'
	hex.Encode(res[14:18], buf[6:8])
	res[18] = '-'
	hex.Encode(res[19:23], buf[8:10])
	res[23] = '-'
	hex.Encode(res[24:], buf[10:])
	return string(res)
}

func NewRequestId() string {
	return NewUUID()
}

func StringValue(p *string) string {
	if p == nil {
		return ""
	}
	return *p
}

func Int32PtrToString(p *int32) string {
	if p == nil {
		return ""
	}
	return strconv.FormatInt(int64(*p), 10)
}
func PtrInt(v int) *int             { return &v }
func PtrInt32(v int32) *int32       { return &v }
func PtrInt64(v int64) *int64       { return &v }
func PtrFloat32(v float32) *float32 { return &v }
func PtrFloat64(v float64) *float64 { return &v }
func PtrBool(v bool) *bool          { return &v }
func PtrString(v string) *string    { return &v }
