// Copyright 2015 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package git

import (
	"bytes"
	"context"
	"encoding/base64"
	"errors"
	"io"
	"strings"

	"gitea.dev/modules/util"
)

// This file contains common functions between the gogit and !gogit variants for git Blobs

// Name returns name of the tree entry this blob object was created from (or empty string)
func (b *Blob) Name() string {
	return b.name
}

// GetBlobBytes Gets the limited content of the blob
func (b *Blob) GetBlobBytes(ctx context.Context, limit int64) ([]byte, error) {
	if limit <= 0 {
		return nil, nil
	}
	dataRc, err := b.DataAsync(ctx)
	if err != nil {
		return nil, err
	}
	defer dataRc.Close()
	return util.ReadWithLimit(dataRc, int(limit))
}

// GetBlobContent Gets the limited content of the blob as raw text
func (b *Blob) GetBlobContent(ctx context.Context, limit int64) (string, error) {
	buf, err := b.GetBlobBytes(ctx, limit)
	return string(buf), err
}

// GetBlobLineCount gets line count of the blob.
// It will also try to write the content to w if it's not nil, then we could pre-fetch the content without reading it again.
func (b *Blob) GetBlobLineCount(ctx context.Context, w io.Writer) (size int64, count int, _ error) {
	reader, err := b.DataAsync(ctx)
	if err != nil {
		return 0, 0, err
	}
	defer reader.Close()
	buf := make([]byte, 32*1024)
	size, count = 0, 1
	lineSep := []byte{'\n'}
	for {
		c, err := reader.Read(buf)
		size += int64(c)
		if w != nil {
			if _, err := w.Write(buf[:c]); err != nil {
				return size, count, err
			}
		}
		count += bytes.Count(buf[:c], lineSep)
		switch {
		case errors.Is(err, io.EOF):
			return size, count, nil
		case err != nil:
			return size, count, err
		}
	}
}

// GetBlobContentBase64 Reads the content of the blob with a base64 encoding and returns the encoded string
func (b *Blob) GetBlobContentBase64(ctx context.Context, originContent *strings.Builder) (string, error) {
	dataRc, err := b.DataAsync(ctx)
	if err != nil {
		return "", err
	}
	defer dataRc.Close()

	base64buf := &strings.Builder{}
	encoder := base64.NewEncoder(base64.StdEncoding, base64buf)
	buf := make([]byte, 32*1024)
loop:
	for {
		n, err := dataRc.Read(buf)
		if n > 0 {
			if originContent != nil {
				_, _ = originContent.Write(buf[:n])
			}
			if _, err := encoder.Write(buf[:n]); err != nil {
				return "", err
			}
		}
		switch {
		case errors.Is(err, io.EOF):
			break loop
		case err != nil:
			return "", err
		}
	}
	_ = encoder.Close()
	return base64buf.String(), nil
}
