/*
 * Copyright 2021 CloudWeGo Authors
 *
 * 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 thrift

import (
	"context"

	"github.com/cloudwego/gopkg/bufiox"
	"github.com/cloudwego/gopkg/protocol/thrift"
	"github.com/cloudwego/gopkg/protocol/thrift/base"

	"github.com/cloudwego/kitex/pkg/generic/descriptor"
)

type StructReaderWriter struct {
	*ReadStruct
	*WriteStruct
}

func NewStructReaderWriter(svc *descriptor.ServiceDescriptor) *StructReaderWriter {
	return &StructReaderWriter{ReadStruct: NewReadStruct(svc), WriteStruct: NewWriteStruct(svc)}
}

func NewStructReaderWriterForJSON(svc *descriptor.ServiceDescriptor) *StructReaderWriter {
	return &StructReaderWriter{ReadStruct: NewReadStructForJSON(svc), WriteStruct: NewWriteStruct(svc)}
}

// NewWriteStruct ...
func NewWriteStruct(svc *descriptor.ServiceDescriptor) *WriteStruct {
	return &WriteStruct{svcDsc: svc}
}

// WriteStruct implement of MessageWriter
type WriteStruct struct {
	svcDsc           *descriptor.ServiceDescriptor
	binaryWithBase64 bool
}

var _ MessageWriter = (*WriteStruct)(nil)

// SetBinaryWithBase64 enable/disable Base64 decoding for binary.
// Note that this method is not concurrent-safe.
func (m *WriteStruct) SetBinaryWithBase64(enable bool) {
	m.binaryWithBase64 = enable
}

// Write ...
func (m *WriteStruct) Write(ctx context.Context, out bufiox.Writer, msg interface{}, method string, isClient bool, requestBase *base.Base) error {
	fnDsc, err := m.svcDsc.LookupFunctionByMethod(method)
	if err != nil {
		return err
	}
	ty := fnDsc.Request
	if !isClient {
		ty = fnDsc.Response
	}
	hasRequestBase := fnDsc.HasRequestBase && isClient

	if !hasRequestBase {
		requestBase = nil
	}
	binaryWriter := thrift.NewBufferWriter(out)
	defer binaryWriter.Recycle()

	if fnDsc.IsWithoutWrapping {
		return structWriter(ctx, msg, binaryWriter, ty, &writerOption{requestBase: requestBase, binaryWithBase64: m.binaryWithBase64})
	} else {
		return wrapStructWriter(ctx, msg, binaryWriter, ty, &writerOption{requestBase: requestBase, binaryWithBase64: m.binaryWithBase64})
	}
}

// NewReadStruct ...
func NewReadStruct(svc *descriptor.ServiceDescriptor) *ReadStruct {
	return &ReadStruct{svc: svc}
}

func NewReadStructForJSON(svc *descriptor.ServiceDescriptor) *ReadStruct {
	return &ReadStruct{
		svc:     svc,
		forJSON: true,
	}
}

// ReadStruct implement of MessageReaderWithMethod
type ReadStruct struct {
	svc                     *descriptor.ServiceDescriptor
	forJSON                 bool
	binaryWithBase64        bool
	binaryWithByteSlice     bool
	setFieldsForEmptyStruct uint8
}

var _ MessageReader = (*ReadStruct)(nil)

// SetBinaryOption enable/disable Base64 encoding or returning []byte for binary.
// Note that this method is not concurrent-safe.
func (m *ReadStruct) SetBinaryOption(base64, byteSlice bool) {
	m.binaryWithBase64 = base64
	m.binaryWithByteSlice = byteSlice
}

// SetBinaryOption enable/disable set all fields in map-generic even if a struct is empty
//
//	mode == 0 means disable
//	mode == 1 means only set required and default fields
//	mode == 2 means set all fields
func (m *ReadStruct) SetSetFieldsForEmptyStruct(mode uint8) {
	m.setFieldsForEmptyStruct = mode
}

// Read ...
func (m *ReadStruct) Read(ctx context.Context, method string, isClient bool, dataLen int, in bufiox.Reader) (interface{}, error) {
	fnDsc, err := m.svc.LookupFunctionByMethod(method)
	if err != nil {
		return nil, err
	}
	fDsc := fnDsc.Response
	if !isClient {
		fDsc = fnDsc.Request
	}
	br := thrift.NewBufferReader(in)
	defer br.Recycle()

	if fnDsc.IsWithoutWrapping {
		return readStruct(ctx, br, fDsc, &readerOption{throwException: true, forJSON: m.forJSON, binaryWithBase64: m.binaryWithBase64, binaryWithByteSlice: m.binaryWithByteSlice, setFieldsForEmptyStruct: m.setFieldsForEmptyStruct})
	} else {
		return skipStructReader(ctx, br, fDsc, &readerOption{throwException: true, forJSON: m.forJSON, binaryWithBase64: m.binaryWithBase64, binaryWithByteSlice: m.binaryWithByteSlice, setFieldsForEmptyStruct: m.setFieldsForEmptyStruct})
	}
}
