package error

import (
	"fmt"
)

const (
	ErrStorageFailure int = 1000 + iota
	ErrGameNotInitialized
	ErrGameStateInvalid
	ErrReportNotFound
)

const (
	ErrDummy int = -1

	ErrDeleteShipTypeExistingGroup    = 5000
	ErrDeleteShipTypePlanetProduction = 5001
	ErrDeleteSciencePlanetProduction  = 5002
	ErrMergeShipTypeNotEqual          = 5003
	ErrBeakGroupNumberNotEnough       = 5005
	ErrEntityInUse                    = 5006
	ErrShipsBusy                      = 5007
	ErrShipsNotOnSamePlanet           = 5008
	ErrUpgradeGroupNumberNotEnough    = 5010
	ErrUpgradeInsufficientResources   = 5011
	ErrSendShipHasNoDrives            = 5012
	ErrSendUnreachableDestination     = 5013
	ErrSendShipOwnerHasNoPlanets      = 5014
	ErrRaceExinct                     = 5015
)

const (
	ErrInputUnknownRace int = 3000 + iota
	ErrInputUnknownRelation
	ErrInputSameRace
	ErrInputEntityTypeNameInvalid
	ErrInputNewEntityDuplicateIdentifier
	ErrInputEntityTypeNameEquality
	ErrInputEntityNotExists
	ErrInputEntityNotOwned
	ErrInputPlanetNumber
	ErrInputDriveValue
	ErrInputWeaponsValue
	ErrInputShieldsValue
	ErrInputCargoValue
	ErrInputShipTypeArmamentValue
	ErrInputShipTypeWeaponsAndArmamentValue
	ErrInputShipTypeZeroValues
	ErrInputScienceSumValues
	ErrInputProductionInvalid
	ErrInputCargoTypeInvalid
	ErrInputCargoLoadNotEnough
	ErrInputCargoLoadNotEqual
	ErrInputNoCargoBay
	ErrInputCargoLoadNoSpaceLeft
	ErrInputCargoUnloadEmpty
	ErrInputBreakGroupIllegalNumber
	ErrInputTechUnknown
	ErrInputTechInvalidMixing
	ErrInputUpgradeShipTechNotUsed
	ErrInputUpgradeParameterNotAllowed
	ErrInputUpgradeShipsAlreadyUpToDate
	ErrInputUpgradeTechLevelInsufficient
	ErrInputQuitCommandFollowedByCommand
	ErrInputUnrecognizedCommand
)

func GenericErrorText(code int) string {
	switch code {
	case ErrDummy:
		return "Dummy"
	case ErrStorageFailure:
		return "Storage failure"
	case ErrGameNotInitialized:
		return "Game not yet initialized"
	case ErrGameStateInvalid:
		return "Invalid game state"
	case ErrInputUnknownRace:
		return "Race name is unknown to this game"
	case ErrInputUnknownRelation:
		return "Unknown relation"
	case ErrInputSameRace:
		return "Race name must be different from your own"
	case ErrInputEntityTypeNameInvalid:
		return "Name has invalid length or symbols"
	case ErrInputNewEntityDuplicateIdentifier:
		return "Entity already exists"
	case ErrInputEntityTypeNameEquality:
		return "Names should differ"
	case ErrInputEntityNotExists:
		return "Entity does not exists"
	case ErrInputEntityNotOwned:
		return "Entity is not owned"
	case ErrEntityInUse:
		return "Entity currently in use"
	case ErrInputPlanetNumber:
		return "Invalid Planet number"
	case ErrInputDriveValue:
		return "Invalid Drive value"
	case ErrInputWeaponsValue:
		return "Invalid Weapons value"
	case ErrInputShieldsValue:
		return "Invalid Shields value"
	case ErrInputCargoValue:
		return "Invalid Cargo value"
	case ErrInputShipTypeArmamentValue:
		return "Invalid Armament value"
	case ErrInputShipTypeWeaponsAndArmamentValue:
		return "Invalid Armament or Weapons value"
	case ErrInputShipTypeZeroValues:
		return "Ship type values cannot be all zeros"
	case ErrDeleteShipTypeExistingGroup:
		return "Ship type exists in a Group"
	case ErrDeleteShipTypePlanetProduction:
		return "Ship type in production on the Planet"
	case ErrDeleteSciencePlanetProduction:
		return "Science in production on the Planet"
	case ErrInputScienceSumValues:
		return "Science proportions sum should be equal 1"
	case ErrInputProductionInvalid:
		return "Invalid Production type"
	case ErrInputCargoTypeInvalid:
		return "Invalid cargo type"
	case ErrInputCargoLoadNotEnough:
		return "Not enough cargo to load"
	case ErrInputCargoLoadNotEqual:
		return "Ship(s) already loaded with another cargo"
	case ErrInputNoCargoBay:
		return "Ship type is not designed to carry cargo"
	case ErrInputCargoLoadNoSpaceLeft:
		return "No space left on the ships to load cargo"
	case ErrInputCargoUnloadEmpty:
		return "Ships are not carrying any cargo"
	case ErrInputBreakGroupIllegalNumber:
		return "Illegal ships number to make new group"
	case ErrMergeShipTypeNotEqual:
		return "Source and target ship types are not the same"
	case ErrBeakGroupNumberNotEnough:
		return "Not enough ships in the group to make a separate group"
	case ErrShipsBusy:
		return "Ship(s) are'n free to use"
	case ErrShipsNotOnSamePlanet:
		return "Ships not on the same planet"
	case ErrInputTechUnknown:
		return "Technology name unknown"
	case ErrInputTechInvalidMixing:
		return "Technologies list must containt only specific values"
	case ErrInputUpgradeShipTechNotUsed:
		return "Technology is not used with ship class"
	case ErrInputUpgradeParameterNotAllowed:
		return "Parameter not allowed for upgrade"
	case ErrInputUpgradeShipsAlreadyUpToDate:
		return "Ships already up to date, nothing to upgrade"
	case ErrUpgradeGroupNumberNotEnough:
		return "Not enough ships in the group to make an upgrade"
	case ErrUpgradeInsufficientResources:
		return "Insufficient planet production capacity"
	case ErrInputUpgradeTechLevelInsufficient:
		return "Insifficient Tech level for requested upgrade"
	case ErrInputQuitCommandFollowedByCommand:
		return "'Quit' must be the last order's command"
	case ErrInputUnrecognizedCommand:
		return "Unrecognized command"
	case ErrSendShipHasNoDrives:
		return "One or more ships are not equipped with hyperdrive and cannot be moved"
	case ErrSendUnreachableDestination:
		return "Destination planet is too far for current Drive level"
	case ErrSendShipOwnerHasNoPlanets:
		return "Race is not owning any planet, all flights impossible"
	case ErrRaceExinct:
		return "Race is extinct"
	default:
		return fmt.Sprintf("Undescribed error with code %d", code)
	}
}

type GenericError struct {
	Code    int
	subject string
	err     error
}

func (ge GenericError) Error() string {
	msg := GenericErrorText(ge.Code)
	if ge.subject != "" {
		msg += ": " + ge.subject
	}
	if ge.err != nil {
		msg = fmt.Errorf("%s: %w", msg, ge.err).Error()
	}
	return msg
}

// Unwrap returns the underlying error wrapped by GenericError, if any.
func (ge GenericError) Unwrap() error {
	return ge.err
}

func newGenericError(code int, arg ...any) error {
	e := &GenericError{Code: code}
	if len(arg) > 0 {
		i := 0
		switch arg[i].(type) {
		case error:
			e.err = arg[i].(error)
			i += 1
		}
		if len(arg) >= i+2 {
			e.subject = fmt.Sprintf(asString(arg[i]), arg[i+1:]...)
		} else if len(arg) == i+1 {
			e.subject = asString(arg[i])
		}
	}
	return e
}

func asString(v any) string {
	switch s := v.(type) {
	case string:
		return s
	default:
		return fmt.Sprint(v)
	}
}
