/*
 * 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 thrift

import (
	"context"
	"strings"
)

const (
	UNKNOWN_APPLICATION_EXCEPTION  = 0
	UNKNOWN_METHOD                 = 1
	INVALID_MESSAGE_TYPE_EXCEPTION = 2
	WRONG_METHOD_NAME              = 3
	BAD_SEQUENCE_ID                = 4
	MISSING_RESULT                 = 5
	INTERNAL_ERROR                 = 6
	PROTOCOL_ERROR                 = 7
	INVALID_TRANSFORM              = 8
	INVALID_PROTOCOL               = 9
	UNSUPPORTED_CLIENT_TYPE        = 10
	VALIDATION_FAILED              = 11
)

var defaultApplicationExceptionMessage = map[int32]string{
	UNKNOWN_APPLICATION_EXCEPTION:  "unknown application exception",
	UNKNOWN_METHOD:                 "unknown method",
	INVALID_MESSAGE_TYPE_EXCEPTION: "invalid message type",
	WRONG_METHOD_NAME:              "wrong method name",
	BAD_SEQUENCE_ID:                "bad sequence ID",
	MISSING_RESULT:                 "missing result",
	INTERNAL_ERROR:                 "unknown internal error",
	PROTOCOL_ERROR:                 "unknown protocol error",
	INVALID_TRANSFORM:              "Invalid transform",
	INVALID_PROTOCOL:               "Invalid protocol",
	UNSUPPORTED_CLIENT_TYPE:        "Unsupported client type",
	VALIDATION_FAILED:              "validation failed",
}

// Application level Thrift exception
type TApplicationException interface {
	TException
	TypeId() int32
	Read(ctx context.Context, iprot TProtocol) error
	Write(ctx context.Context, oprot TProtocol) error
}

type ValidationError struct {
	message     string
	check       string
	fieldSymbol string
}

func (e *ValidationError) Check() string {
	return e.check
}

func (e *ValidationError) TypeName() string {
	return strings.Split(e.fieldSymbol, ".")[0]
}

func (e *ValidationError) Field() string {
	if fs := strings.Split(e.fieldSymbol, "."); len(fs) > 1 {
		return fs[1]
	}
	return e.fieldSymbol
}

func (e *ValidationError) FieldSymbol() string {
	return e.fieldSymbol
}

func (e ValidationError) Error() string {
	return e.message
}

type tApplicationException struct {
	message string
	type_   int32
	err     error
}

var _ TApplicationException = (*tApplicationException)(nil)

func (tApplicationException) TExceptionType() TExceptionType {
	return TExceptionTypeApplication
}

func (e tApplicationException) Error() string {
	if e.message != "" {
		return e.message
	}
	return defaultApplicationExceptionMessage[e.type_]
}

func (e tApplicationException) Unwrap() error {
	return e.err
}

func NewTApplicationException(type_ int32, message string) TApplicationException {
	return &tApplicationException{message, type_, nil}
}

func NewValidationException(type_ int32, check string, field string, message string) TApplicationException {
	return &tApplicationException{
		type_:   type_,
		message: message,
		err:     &ValidationError{message: message, check: check, fieldSymbol: field},
	}
}

func (p *tApplicationException) TypeId() int32 {
	return p.type_
}

func (p *tApplicationException) Read(ctx context.Context, iprot TProtocol) error {
	// TODO: this should really be generated by the compiler
	_, err := iprot.ReadStructBegin(ctx)
	if err != nil {
		return err
	}

	message := ""
	type_ := int32(UNKNOWN_APPLICATION_EXCEPTION)

	for {
		_, ttype, id, err := iprot.ReadFieldBegin(ctx)
		if err != nil {
			return err
		}
		if ttype == STOP {
			break
		}
		switch id {
		case 1:
			if ttype == STRING {
				if message, err = iprot.ReadString(ctx); err != nil {
					return err
				}
			} else {
				if err = SkipDefaultDepth(ctx, iprot, ttype); err != nil {
					return err
				}
			}
		case 2:
			if ttype == I32 {
				if type_, err = iprot.ReadI32(ctx); err != nil {
					return err
				}
			} else {
				if err = SkipDefaultDepth(ctx, iprot, ttype); err != nil {
					return err
				}
			}
		default:
			if err = SkipDefaultDepth(ctx, iprot, ttype); err != nil {
				return err
			}
		}
		if err = iprot.ReadFieldEnd(ctx); err != nil {
			return err
		}
	}
	if err := iprot.ReadStructEnd(ctx); err != nil {
		return err
	}

	p.message = message
	p.type_ = type_

	return nil
}

func (p *tApplicationException) Write(ctx context.Context, oprot TProtocol) (err error) {
	err = oprot.WriteStructBegin(ctx, "TApplicationException")
	if err != nil {
		return
	}
	if len(p.Error()) > 0 {
		err = oprot.WriteFieldBegin(ctx, "message", STRING, 1)
		if err != nil {
			return
		}
		err = oprot.WriteString(ctx, p.Error())
		if err != nil {
			return
		}
		err = oprot.WriteFieldEnd(ctx)
		if err != nil {
			return
		}
	}
	err = oprot.WriteFieldBegin(ctx, "type", I32, 2)
	if err != nil {
		return
	}
	err = oprot.WriteI32(ctx, p.type_)
	if err != nil {
		return
	}
	err = oprot.WriteFieldEnd(ctx)
	if err != nil {
		return
	}
	err = oprot.WriteFieldStop(ctx)
	if err != nil {
		return
	}
	err = oprot.WriteStructEnd(ctx)
	return
}
