# coding: utf-8

"""
    LUSID API

    FINBOURNE Technology  # noqa: E501

    Contact: info@finbourne.com
    Generated by OpenAPI Generator (https://openapi-generator.tech)

    Do not edit the class manually.
"""


from __future__ import annotations
import pprint
import re  # noqa: F401
import json


from typing import List, Dict, Optional, Any, Union, TYPE_CHECKING
from typing_extensions import Annotated
from pydantic.v1 import BaseModel, StrictStr, StrictInt, StrictBool, StrictFloat, StrictBytes, Field, validator, ValidationError, conlist, constr
from datetime import datetime
from lusid.models.ex_dividend_configuration import ExDividendConfiguration
from lusid.models.flow_conventions import FlowConventions
from lusid.models.lusid_instrument import LusidInstrument
from lusid.models.rounding_convention import RoundingConvention
from lusid.models.time_zone_conventions import TimeZoneConventions
from lusid.models.trading_conventions import TradingConventions

class Bond(LusidInstrument):
    """
    LUSID representation of a Vanilla Fixed Rate Bond.  # noqa: E501
    """
    start_date: datetime = Field(description="The Start date of the bond, this is normally when accrual of the first coupon begins.", alias="startDate")
    maturity_date: datetime = Field(description="The Maturity date of the bond, this is when the last coupon accrual period ends.  Note that while most bonds have their last payment on this date there are some cases where the final payment is the next working day.", alias="maturityDate")
    dom_ccy:  StrictStr = Field(...,alias="domCcy", description="The domestic currency of the instrument. This should be the same as the Currency set on the FlowConventions.") 
    flow_conventions: FlowConventions = Field(alias="flowConventions")
    principal: Union[StrictFloat, StrictInt] = Field(description="The face-value or principal for the bond at outset.  This might be reduced through its lifetime in the event of amortisation or similar.")
    coupon_rate: Union[StrictFloat, StrictInt] = Field(description="Simple coupon rate.", alias="couponRate")
    identifiers: Optional[Dict[str, Optional[StrictStr]]] = Field(default=None, description="External market codes and identifiers for the bond, e.g. ISIN.")
    ex_dividend_days: Optional[StrictInt] = Field(default=None, description="Optional. Number of calendar days in the ex-dividend period.  If the settlement date falls in the ex-dividend period then the coupon paid is zero and the accrued interest is negative.  If set, this must be a non-negative number.  If not set, or set to 0, then there is no ex-dividend period.                NOTE: This field is deprecated.  If you wish to set the ExDividendDays on a bond, please use the ExDividendConfiguration.", alias="exDividendDays")
    initial_coupon_date: Optional[datetime] = Field(default=None, description="Optional and to be DEPRECATED. If set, this is the date at which the bond begins to accrue interest. Instead, this information should be entered in the field StartDate.", alias="initialCouponDate")
    first_coupon_pay_date: Optional[datetime] = Field(default=None, description="The date that the first coupon of the bond is paid. This is required for bonds that have a long first coupon or short first coupon. The first coupon pay date is used  as an anchor to compare with the start date and determine if this is a long/short coupon period.", alias="firstCouponPayDate")
    calculation_type:  Optional[StrictStr] = Field(None,alias="calculationType", description="The calculation type applied to the bond coupon amount. This is required for bonds that have a particular type of computing the period coupon, such as simple compounding,  irregular coupons etc.  The default CalculationType is `Standard`, which returns a coupon amount equal to Principal * Coupon Rate / Coupon Frequency. Coupon Frequency is 12M / Payment Frequency.  Payment Frequency can be 1M, 3M, 6M, 12M etc. So Coupon Frequency can be 12, 4, 2, 1 respectively.    Supported string (enumeration) values are: [Standard, DayCountCoupon, NoCalculationFloater, BrazilFixedCoupon, StandardWithCappedAccruedInterest].") 
    rounding_conventions: Optional[List[RoundingConvention]] = Field(default=None, description="Rounding conventions for analytics, if any.", alias="roundingConventions")
    ex_dividend_configuration: Optional[ExDividendConfiguration] = Field(default=None, alias="exDividendConfiguration")
    original_issue_price: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="The price the bond was issued at. This is to be entered as a percentage of par, for example a value of 98.5 would represent 98.5%.", alias="originalIssuePrice")
    trading_conventions: Optional[TradingConventions] = Field(default=None, alias="tradingConventions")
    time_zone_conventions: Optional[TimeZoneConventions] = Field(default=None, alias="timeZoneConventions")
    instrument_type:  StrictStr = Field(...,alias="instrumentType", description="The available values are: QuotedSecurity, InterestRateSwap, FxForward, Future, ExoticInstrument, FxOption, CreditDefaultSwap, InterestRateSwaption, Bond, EquityOption, FixedLeg, FloatingLeg, BespokeCashFlowsLeg, Unknown, TermDeposit, ContractForDifference, EquitySwap, CashPerpetual, CapFloor, CashSettled, CdsIndex, Basket, FundingLeg, FxSwap, ForwardRateAgreement, SimpleInstrument, Repo, Equity, ExchangeTradedOption, ReferenceInstrument, ComplexBond, InflationLinkedBond, InflationSwap, SimpleCashFlowLoan, TotalReturnSwap, InflationLeg, FundShareClass, FlexibleLoan, UnsettledCash, Cash, MasteredInstrument, LoanFacility, FlexibleDeposit, FlexibleRepo") 
    additional_properties: Dict[str, Any] = {}
    __properties = ["instrumentType", "startDate", "maturityDate", "domCcy", "flowConventions", "principal", "couponRate", "identifiers", "exDividendDays", "initialCouponDate", "firstCouponPayDate", "calculationType", "roundingConventions", "exDividendConfiguration", "originalIssuePrice", "tradingConventions", "timeZoneConventions"]

    @validator('instrument_type')
    def instrument_type_validate_enum(cls, value):
        """Validates the enum"""

        # Finbourne have removed enum validation on all models, except for this use case:
        # Workflow and notification application SDK use the property name 'type' as the discriminator on a number of classes.
        # During instantiation, the value of 'type' is checked against the enum values, 
        

        # check it's a class that uses the 'type' property as a discriminator
        # list of classes can be found by searching for 'actual_instance: Union[' in the generated code
        if 'Bond' not in [ 
                                    # For notification application classes
                                    'AmazonSqsNotificationType',
                                    'AmazonSqsNotificationTypeResponse',
                                    'AmazonSqsPrincipalAuthNotificationType',
                                    'AmazonSqsPrincipalAuthNotificationTypeResponse',
                                    'AzureServiceBusTypeResponse',
                                    'AzureServiceBusNotificationType',
                                    'EmailNotificationType',
                                    'EmailNotificationTypeResponse',
                                    'SmsNotificationType',
                                    'SmsNotificationTypeResponse',
                                    'WebhookNotificationType',
                                    'WebhookNotificationTypeResponse',
                        
                                    # For workflow application classes
                                    'CreateChildTasksAction', 
                                    'RunWorkerAction', 
                                    'TriggerParentTaskAction',
                                    'CreateChildTasksActionResponse', 
                                    'RunWorkerActionResponse',
                                    'TriggerParentTaskActionResponse',
                                    'CreateNewTaskActivity',
                                    'UpdateMatchingTasksActivity',
                                    'CreateNewTaskActivityResponse', 
                                    'UpdateMatchingTasksActivityResponse',
                                    'Fail', 
                                    'GroupReconciliation', 
                                    'HealthCheck', 
                                    'LuminesceView', 
                                    'SchedulerJob', 
                                    'Sleep',
                                    'FailResponse', 
                                    'GroupReconciliationResponse', 
                                    'HealthCheckResponse', 
                                    'LuminesceViewResponse', 
                                    'SchedulerJobResponse', 
                                    'SleepResponse',
                                    'Library',
                                    'LibraryResponse',
                                    'DayRegularity',
                                    'RelativeMonthRegularity',
                                    'SpecificMonthRegularity',
                                    'WeekRegularity',
                                    'YearRegularity']:
           return value
        
        # Only validate the 'type' property of the class
        if "instrument_type" != "type":
            return value

        if value not in ['QuotedSecurity', 'InterestRateSwap', 'FxForward', 'Future', 'ExoticInstrument', 'FxOption', 'CreditDefaultSwap', 'InterestRateSwaption', 'Bond', 'EquityOption', 'FixedLeg', 'FloatingLeg', 'BespokeCashFlowsLeg', 'Unknown', 'TermDeposit', 'ContractForDifference', 'EquitySwap', 'CashPerpetual', 'CapFloor', 'CashSettled', 'CdsIndex', 'Basket', 'FundingLeg', 'FxSwap', 'ForwardRateAgreement', 'SimpleInstrument', 'Repo', 'Equity', 'ExchangeTradedOption', 'ReferenceInstrument', 'ComplexBond', 'InflationLinkedBond', 'InflationSwap', 'SimpleCashFlowLoan', 'TotalReturnSwap', 'InflationLeg', 'FundShareClass', 'FlexibleLoan', 'UnsettledCash', 'Cash', 'MasteredInstrument', 'LoanFacility', 'FlexibleDeposit', 'FlexibleRepo']:
            raise ValueError("must be one of enum values ('QuotedSecurity', 'InterestRateSwap', 'FxForward', 'Future', 'ExoticInstrument', 'FxOption', 'CreditDefaultSwap', 'InterestRateSwaption', 'Bond', 'EquityOption', 'FixedLeg', 'FloatingLeg', 'BespokeCashFlowsLeg', 'Unknown', 'TermDeposit', 'ContractForDifference', 'EquitySwap', 'CashPerpetual', 'CapFloor', 'CashSettled', 'CdsIndex', 'Basket', 'FundingLeg', 'FxSwap', 'ForwardRateAgreement', 'SimpleInstrument', 'Repo', 'Equity', 'ExchangeTradedOption', 'ReferenceInstrument', 'ComplexBond', 'InflationLinkedBond', 'InflationSwap', 'SimpleCashFlowLoan', 'TotalReturnSwap', 'InflationLeg', 'FundShareClass', 'FlexibleLoan', 'UnsettledCash', 'Cash', 'MasteredInstrument', 'LoanFacility', 'FlexibleDeposit', 'FlexibleRepo')")
        return value

    class Config:
        """Pydantic configuration"""
        allow_population_by_field_name = True
        validate_assignment = True

    def __str__(self):
        """For `print` and `pprint`"""
        return pprint.pformat(self.dict(by_alias=False))

    def __repr__(self):
        """For `print` and `pprint`"""
        return self.to_str()

    def to_str(self) -> str:
        """Returns the string representation of the model using alias"""
        return pprint.pformat(self.dict(by_alias=True))

    def to_json(self) -> str:
        """Returns the JSON representation of the model using alias"""
        return json.dumps(self.to_dict())

    @classmethod
    def from_json(cls, json_str: str) -> Bond:
        """Create an instance of Bond from a JSON string"""
        return cls.from_dict(json.loads(json_str))

    def to_dict(self):
        """Returns the dictionary representation of the model using alias"""
        _dict = self.dict(by_alias=True,
                          exclude={
                            "additional_properties"
                          },
                          exclude_none=True)
        # override the default output from pydantic by calling `to_dict()` of flow_conventions
        if self.flow_conventions:
            _dict['flowConventions'] = self.flow_conventions.to_dict()
        # override the default output from pydantic by calling `to_dict()` of each item in rounding_conventions (list)
        _items = []
        if self.rounding_conventions:
            for _item in self.rounding_conventions:
                if _item:
                    _items.append(_item.to_dict())
            _dict['roundingConventions'] = _items
        # override the default output from pydantic by calling `to_dict()` of ex_dividend_configuration
        if self.ex_dividend_configuration:
            _dict['exDividendConfiguration'] = self.ex_dividend_configuration.to_dict()
        # override the default output from pydantic by calling `to_dict()` of trading_conventions
        if self.trading_conventions:
            _dict['tradingConventions'] = self.trading_conventions.to_dict()
        # override the default output from pydantic by calling `to_dict()` of time_zone_conventions
        if self.time_zone_conventions:
            _dict['timeZoneConventions'] = self.time_zone_conventions.to_dict()
        # puts key-value pairs in additional_properties in the top level
        if self.additional_properties is not None:
            for _key, _value in self.additional_properties.items():
                _dict[_key] = _value

        # set to None if identifiers (nullable) is None
        # and __fields_set__ contains the field
        if self.identifiers is None and "identifiers" in self.__fields_set__:
            _dict['identifiers'] = None

        # set to None if ex_dividend_days (nullable) is None
        # and __fields_set__ contains the field
        if self.ex_dividend_days is None and "ex_dividend_days" in self.__fields_set__:
            _dict['exDividendDays'] = None

        # set to None if initial_coupon_date (nullable) is None
        # and __fields_set__ contains the field
        if self.initial_coupon_date is None and "initial_coupon_date" in self.__fields_set__:
            _dict['initialCouponDate'] = None

        # set to None if first_coupon_pay_date (nullable) is None
        # and __fields_set__ contains the field
        if self.first_coupon_pay_date is None and "first_coupon_pay_date" in self.__fields_set__:
            _dict['firstCouponPayDate'] = None

        # set to None if calculation_type (nullable) is None
        # and __fields_set__ contains the field
        if self.calculation_type is None and "calculation_type" in self.__fields_set__:
            _dict['calculationType'] = None

        # set to None if rounding_conventions (nullable) is None
        # and __fields_set__ contains the field
        if self.rounding_conventions is None and "rounding_conventions" in self.__fields_set__:
            _dict['roundingConventions'] = None

        # set to None if original_issue_price (nullable) is None
        # and __fields_set__ contains the field
        if self.original_issue_price is None and "original_issue_price" in self.__fields_set__:
            _dict['originalIssuePrice'] = None

        return _dict

    @classmethod
    def from_dict(cls, obj: dict) -> Bond:
        """Create an instance of Bond from a dict"""
        if obj is None:
            return None

        if not isinstance(obj, dict):
            return Bond.parse_obj(obj)

        _obj = Bond.parse_obj({
            "instrument_type": obj.get("instrumentType"),
            "start_date": obj.get("startDate"),
            "maturity_date": obj.get("maturityDate"),
            "dom_ccy": obj.get("domCcy"),
            "flow_conventions": FlowConventions.from_dict(obj.get("flowConventions")) if obj.get("flowConventions") is not None else None,
            "principal": obj.get("principal"),
            "coupon_rate": obj.get("couponRate"),
            "identifiers": obj.get("identifiers"),
            "ex_dividend_days": obj.get("exDividendDays"),
            "initial_coupon_date": obj.get("initialCouponDate"),
            "first_coupon_pay_date": obj.get("firstCouponPayDate"),
            "calculation_type": obj.get("calculationType"),
            "rounding_conventions": [RoundingConvention.from_dict(_item) for _item in obj.get("roundingConventions")] if obj.get("roundingConventions") is not None else None,
            "ex_dividend_configuration": ExDividendConfiguration.from_dict(obj.get("exDividendConfiguration")) if obj.get("exDividendConfiguration") is not None else None,
            "original_issue_price": obj.get("originalIssuePrice"),
            "trading_conventions": TradingConventions.from_dict(obj.get("tradingConventions")) if obj.get("tradingConventions") is not None else None,
            "time_zone_conventions": TimeZoneConventions.from_dict(obj.get("timeZoneConventions")) if obj.get("timeZoneConventions") is not None else None
        })
        # store additional fields in additional_properties
        for _key in obj.keys():
            if _key not in cls.__properties:
                _obj.additional_properties[_key] = obj.get(_key)

        return _obj

Bond.update_forward_refs()
