problemmediumooddesign-vehicle-parking-systemdesign vehicle parking systemdesignvehicleparkingsystemobject-oriented-design-for-a-parking-lotobject oriented design for a parking lotobjectorienteddesignforaparkinglot

OOD - Parking Lot

MediumUpdated: Dec 31, 2025

Problem Statement

Design a comprehensive multi-level parking lot management system that efficiently handles vehicle parking operations across multiple floors. The system must support various vehicle types (motorcycles, cars, trucks, electric vehicles) with corresponding spot types (compact, standard, large, motorcycle, electric with charging).

Key requirements include:

  • Real-time tracking of available parking spots across all floors
  • Automated ticket generation at entry with timestamp
  • Dynamic pricing calculation based on duration, vehicle type, and time of day (peak/off-peak)
  • Multiple payment methods (cash, credit card, mobile payment) at exit
  • Reservation system for advance spot booking
  • Display boards on each floor showing real-time availability by spot type
  • Administrative functions for adding/removing floors, spots, and configuring pricing
  • Support for electric vehicle charging stations with separate billing
  • Handling edge cases: full lot notification, lost ticket scenarios, spot maintenance mode
  • Audit logging for all transactions and system events

Requirements Analysis

Functional Requirements

Core Operations:

  1. Entry Management

    • Issue parking tickets with unique IDs at entry gates
    • Automatically detect vehicle type or allow manual selection
    • Find and assign appropriate available spot based on vehicle type
    • Refuse entry when no suitable spots available
    • Support reservation validation at entry
  2. Exit Management

    • Scan ticket and calculate parking fee based on duration
    • Process payment through multiple methods
    • Release barrier after successful payment
    • Handle lost ticket scenarios with maximum fee
  3. Spot Management

    • Track real-time occupancy status of all spots
    • Support different spot types with size compatibility
    • Mark spots for maintenance (temporarily unavailable)
    • Optimize spot assignment (nearest available)
  4. Pricing

    • Hourly rates with different pricing tiers by vehicle type
    • Peak and off-peak hour pricing
    • Daily maximum cap
    • Special rates for monthly pass holders
    • Separate charging fees for electric vehicles
  5. Reservation System

    • Allow advance booking for specific time slots
    • Guarantee spot availability for reservations
    • Cancellation handling with refund policies
    • Reservation expiration and spot release
  6. Administrative Operations

    • Add/remove/modify parking floors
    • Configure spot types and counts per floor
    • Update pricing strategies
    • Generate reports (revenue, occupancy trends, peak hours)
    • Monitor system health

Non-Functional Requirements

  1. Reliability: Zero ticket loss, accurate fee calculation, consistent state across concurrent operations
  2. Scalability: Support 1000+ spots across 10+ floors with 100s of concurrent entries/exits
  3. Performance: Ticket generation < 2 seconds, fee calculation < 1 second, real-time display updates
  4. Availability: 99.9% uptime, graceful degradation if payment gateway fails
  5. Security: Secure payment processing, tamper-proof tickets, audit trails
  6. Usability: Clear display boards, simple payment interface, multilingual support

Core Use Cases

graph TB
    subgraph Actors
        Customer[๐Ÿ‘ค Customer]
        Attendant[๐Ÿ‘” Parking Attendant]
        Admin[๐Ÿ‘จโ€๐Ÿ’ผ Administrator]
        System[๐Ÿ–ฅ๏ธ System]
    end

    subgraph "Core Use Cases"
        UC1[๐ŸŽซ Get Parking Ticket]
        UC2[๐Ÿ’ณ Pay and Exit]
        UC3[๐Ÿ“… Make Reservation]
        UC4[๐Ÿ” Check Availability]
        UC5[๐Ÿข Manage Floors]
        UC6[๐Ÿ…ฟ๏ธ Manage Spots]
        UC7[๐Ÿ’ฐ Configure Pricing]
        UC8[๐Ÿ“Š Generate Reports]
        UC9[๐Ÿ“บ Update Display Boards]
        UC10[๐Ÿ”Œ Charge Electric Vehicle]
        UC11[๐ŸŽŸ๏ธ Handle Lost Ticket]
        UC12[๐Ÿ”ง Mark Spot Maintenance]
    end

    Customer -->|enters| UC1
    Customer -->|exits| UC2
    Customer -->|books| UC3
    Customer -->|views| UC4
    Customer -->|uses| UC10

    Attendant -->|assists| UC2
    Attendant -->|processes| UC11

    Admin -->|creates| UC5
    Admin -->|configures| UC6
    Admin -->|sets| UC7
    Admin -->|views| UC8
    Admin -->|schedules| UC12

    System -->|automatically| UC9

    style UC1 fill:#e1f5ff,stroke:#01579b,stroke-width:2px
    style UC2 fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
    style UC3 fill:#fff9c4,stroke:#f57f17,stroke-width:2px
    style UC4 fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px
    style UC5 fill:#ffe0b2,stroke:#e65100,stroke-width:2px
    style UC6 fill:#b2dfdb,stroke:#00695c,stroke-width:2px
    style UC7 fill:#ffccbc,stroke:#bf360c,stroke-width:2px
    style UC8 fill:#d1c4e9,stroke:#4527a0,stroke-width:2px
    style UC9 fill:#b2ebf2,stroke:#006064,stroke-width:2px
    style UC10 fill:#c5e1a5,stroke:#558b2f,stroke-width:2px
    style UC11 fill:#ffcdd2,stroke:#c62828,stroke-width:2px
    style UC12 fill:#ffe082,stroke:#f9a825,stroke-width:2px

Class Diagram

The design demonstrates several key patterns:

  • Strategy Pattern: PricingStrategy for flexible pricing algorithms
  • Observer Pattern: DisplayBoard observers get notified on spot availability changes
  • Factory Pattern: ParkingSpotFactory creates appropriate spot types
  • Singleton Pattern: ParkingLot ensures single instance coordination
  • State Pattern: ParkingSpot states (Available, Occupied, Reserved, Maintenance)
classDiagram
    class ParkingLot {
        -String name
        -List~ParkingFloor~ floors
        -List~EntranceGate~ entrances
        -List~ExitGate~ exits
        -PricingStrategy pricingStrategy
        -Map~String,Ticket~ activeTickets
        -ReservationManager reservationManager
        +getInstance() ParkingLot
        +addFloor(floor)
        +getAvailableSpot(vehicleType) ParkingSpot
        +issueTicket(vehicle) Ticket
        +processExit(ticket, payment) Receipt
        +getTotalCapacity() int
        +getAvailableCount(spotType) int
    }

    class ParkingFloor {
        -int floorNumber
        -Map~SpotType,List~ParkingSpot~~ spots
        -DisplayBoard displayBoard
        +addSpot(spot)
        +removeSpot(spotId)
        +findAvailableSpot(vehicleType) ParkingSpot
        +getAvailableCount(spotType) int
        +notifyDisplayBoard()
    }

    class ParkingSpot {
        <<abstract>>
        -String spotId
        -SpotType spotType
        -SpotState state
        -Vehicle vehicle
        +isAvailable() boolean
        +assignVehicle(vehicle)
        +releaseVehicle()
        +setState(state)
        +getType() SpotType
    }

    class CompactSpot {
        +canFitVehicle(vehicle) boolean
    }

    class StandardSpot {
        +canFitVehicle(vehicle) boolean
    }

    class LargeSpot {
        +canFitVehicle(vehicle) boolean
    }

    class MotorcycleSpot {
        +canFitVehicle(vehicle) boolean
    }

    class ElectricSpot {
        -ChargingStation chargingStation
        +canFitVehicle(vehicle) boolean
        +startCharging()
        +stopCharging()
        +getChargingFee() Money
    }

    class SpotState {
        <<enumeration>>
        AVAILABLE
        OCCUPIED
        RESERVED
        MAINTENANCE
        OUT_OF_SERVICE
    }

    class Vehicle {
        <<abstract>>
        -String licensePlate
        -VehicleType type
        +getType() VehicleType
    }

    class Motorcycle {
    }

    class Car {
    }

    class Truck {
    }

    class ElectricCar {
        -int batteryCapacity
        +needsCharging() boolean
    }

    class VehicleType {
        <<enumeration>>
        MOTORCYCLE
        CAR
        TRUCK
        ELECTRIC_CAR
        VAN
    }

    class SpotType {
        <<enumeration>>
        MOTORCYCLE
        COMPACT
        STANDARD
        LARGE
        ELECTRIC
    }

    class Ticket {
        -String ticketId
        -DateTime issueTime
        -ParkingSpot assignedSpot
        -Vehicle vehicle
        -TicketStatus status
        +calculateFee() Money
        +getParkedDuration() Duration
    }

    class TicketStatus {
        <<enumeration>>
        ACTIVE
        PAID
        LOST
        RESERVED
    }

    class PricingStrategy {
        <<interface>>
        +calculateFee(ticket) Money
    }

    class HourlyPricing {
        -Map~VehicleType,Money~ hourlyRates
        -Money maxDailyRate
        +calculateFee(ticket) Money
    }

    class PeakHourPricing {
        -PricingStrategy baseStrategy
        -double peakMultiplier
        -TimeRange peakHours
        +calculateFee(ticket) Money
        +isPeakHour(time) boolean
    }

    class MonthlyPassPricing {
        -Map~String,Pass~ activePasses
        +calculateFee(ticket) Money
        +validatePass(passId) boolean
    }

    class Payment {
        -String paymentId
        -Money amount
        -PaymentMethod method
        -PaymentStatus status
        -DateTime timestamp
        +process() boolean
    }

    class PaymentMethod {
        <<enumeration>>
        CASH
        CREDIT_CARD
        DEBIT_CARD
        MOBILE_PAYMENT
    }

    class DisplayBoard {
        -int floorNumber
        -Map~SpotType,Integer~ availableCounts
        +update(spotType, availableCount)
        +displayAvailability()
    }

    class EntranceGate {
        -String gateId
        +issueTicket(vehicle) Ticket
        +validateReservation(reservationId) boolean
    }

    class ExitGate {
        -String gateId
        -PaymentProcessor paymentProcessor
        +scanTicket(ticketId) Ticket
        +processPayment(ticket, payment) Receipt
        +openBarrier()
    }

    class ReservationManager {
        -Map~String,Reservation~ reservations
        +createReservation(vehicle, duration) Reservation
        +cancelReservation(reservationId)
        +validateReservation(reservationId) boolean
        +findReservedSpot(reservationId) ParkingSpot
    }

    class Reservation {
        -String reservationId
        -Vehicle vehicle
        -DateTime startTime
        -DateTime endTime
        -ParkingSpot reservedSpot
        -ReservationStatus status
        +isValid() boolean
        +cancel()
    }

    class Admin {
        -String adminId
        -String name
        +addFloor(floor)
        +addSpot(floor, spot)
        +updatePricing(strategy)
        +generateReport(type) Report
        +markSpotMaintenance(spotId)
    }

    class ParkingAttendant {
        -String attendantId
        -String name
        +assistPayment(ticket, payment) Receipt
        +handleLostTicket(vehicle) Ticket
        +overrideGate()
    }

    ParkingLot "1" *-- "many" ParkingFloor
    ParkingLot "1" *-- "many" EntranceGate
    ParkingLot "1" *-- "many" ExitGate
    ParkingLot "1" --> "1" PricingStrategy
    ParkingLot "1" --> "1" ReservationManager

    ParkingFloor "1" *-- "many" ParkingSpot
    ParkingFloor "1" --> "1" DisplayBoard

    ParkingSpot <|-- CompactSpot
    ParkingSpot <|-- StandardSpot
    ParkingSpot <|-- LargeSpot
    ParkingSpot <|-- MotorcycleSpot
    ParkingSpot <|-- ElectricSpot

    ParkingSpot --> SpotState
    ParkingSpot --> Vehicle

    Vehicle <|-- Motorcycle
    Vehicle <|-- Car
    Vehicle <|-- Truck
    Vehicle <|-- ElectricCar
    Vehicle --> VehicleType

    Ticket --> ParkingSpot
    Ticket --> Vehicle
    Ticket --> TicketStatus

    PricingStrategy <|.. HourlyPricing
    PricingStrategy <|.. PeakHourPricing
    PricingStrategy <|.. MonthlyPassPricing

    Payment --> PaymentMethod

    ReservationManager "1" *-- "many" Reservation
    Reservation --> ParkingSpot
    Reservation --> Vehicle

    EntranceGate --> Ticket
    ExitGate --> Payment

Activity Diagrams

1. Customer Entry and Parking Flow

flowchart TD
    Start([๐Ÿš— Vehicle Arrives at Entry]) --> CheckReservation{Has\nReservation?}

    CheckReservation -->|Yes| ValidateRes[๐Ÿ“‹ Validate Reservation ID]
    CheckReservation -->|No| DetectVehicle[๐Ÿ” Detect/Scan Vehicle Type]

    ValidateRes --> ResValid{Valid?}
    ResValid -->|Yes| FindResSpot[๐ŸŽฏ Find Reserved Spot]
    ResValid -->|No| DisplayResError[โŒ Show Invalid Reservation]
    DisplayResError --> End1([๐Ÿšซ Entry Denied])

    DetectVehicle --> FindSpot[๐Ÿ” Search Available Spot<br/>for Vehicle Type]

    FindSpot --> SpotAvailable{Spot<br/>Available?}
    SpotAvailable -->|No| DisplayFull[โ›” Display Lot Full Message]
    DisplayFull --> End1

    SpotAvailable -->|Yes| AssignSpot[โœ… Assign Spot to Vehicle]
    FindResSpot --> AssignSpot

    AssignSpot --> UpdateState[๐Ÿ”„ Update Spot State to OCCUPIED]
    UpdateState --> GenerateTicket[๐ŸŽซ Generate Parking Ticket<br/>with Timestamp & Spot Info]

    GenerateTicket --> NotifyDisplay[๐Ÿ“บ Notify Display Board<br/>Decrement Available Count]
    NotifyDisplay --> PrintTicket[๐Ÿ–จ๏ธ Print/Issue Ticket]

    PrintTicket --> OpenBarrier[๐Ÿšง Open Entry Barrier]
    OpenBarrier --> VehicleEnters[๐Ÿš— Vehicle Enters & Parks]

    VehicleEnters --> End2([โœ… Entry Complete])

    style Start fill:#e3f2fd,stroke:#1565c0,stroke-width:3px
    style CheckReservation fill:#fff9c4,stroke:#f57f17,stroke-width:2px
    style ValidateRes fill:#e1bee7,stroke:#6a1b9a,stroke-width:2px
    style DetectVehicle fill:#b2dfdb,stroke:#00695c,stroke-width:2px
    style FindSpot fill:#b3e5fc,stroke:#0277bd,stroke-width:2px
    style SpotAvailable fill:#fff59d,stroke:#f9a825,stroke-width:2px
    style DisplayFull fill:#ffcdd2,stroke:#c62828,stroke-width:2px
    style AssignSpot fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
    style UpdateState fill:#bbdefb,stroke:#1565c0,stroke-width:2px
    style GenerateTicket fill:#dcedc8,stroke:#558b2f,stroke-width:2px
    style NotifyDisplay fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style PrintTicket fill:#c5cae9,stroke:#3949ab,stroke-width:2px
    style OpenBarrier fill:#b2dfdb,stroke:#00796b,stroke-width:2px
    style VehicleEnters fill:#a5d6a7,stroke:#388e3c,stroke-width:2px
    style End1 fill:#ef5350,stroke:#c62828,stroke-width:3px
    style End2 fill:#66bb6a,stroke:#2e7d32,stroke-width:3px
    style DisplayResError fill:#ffccbc,stroke:#d84315,stroke-width:2px

2. Exit and Payment Processing Flow

flowchart TD
    Start([๐Ÿš— Vehicle Arrives at Exit]) --> ScanTicket[๐Ÿ” Scan Parking Ticket]

    ScanTicket --> TicketValid{Ticket<br/>Found?}
    TicketValid -->|No| LostTicket[โ“ Lost Ticket Scenario]
    LostTicket --> AttendantHelp[๐Ÿ‘” Call Attendant]
    AttendantHelp --> VerifyVehicle[๐Ÿ” Verify Vehicle License Plate]
    VerifyVehicle --> MaxFee[๐Ÿ’ฐ Apply Maximum Daily Fee]

    TicketValid -->|Yes| CalcDuration[โฑ๏ธ Calculate Parked Duration]
    CalcDuration --> CheckPeak[๐Ÿ• Check Peak Hour Status]

    CheckPeak --> ApplyPricing[๐Ÿ’ต Apply Pricing Strategy<br/>Hourly/Peak/Monthly Pass]
    MaxFee --> ApplyPricing

    ApplyPricing --> EVCheck{Electric Vehicle<br/>Used Charging?}
    EVCheck -->|Yes| AddChargingFee[โšก Add Charging Fee<br/>Based on kWh Used]
    EVCheck -->|No| CalcTotal[๐Ÿงฎ Calculate Total Fee]
    AddChargingFee --> CalcTotal

    CalcTotal --> DisplayFee[๐Ÿ“บ Display Fee to Customer]
    DisplayFee --> PaymentMethod{Select Payment<br/>Method}

    PaymentMethod -->|๐Ÿ’ณ Card| ProcessCard[๐Ÿ’ณ Process Card Payment<br/>via Payment Gateway]
    PaymentMethod -->|๐Ÿ“ฑ Mobile| ProcessMobile[๐Ÿ“ฑ Process Mobile Payment]
    PaymentMethod -->|๐Ÿ’ต Cash| ProcessCash[๐Ÿ‘” Attendant Collects Cash]

    ProcessCard --> PaymentSuccess{Payment<br/>Successful?}
    ProcessMobile --> PaymentSuccess
    ProcessCash --> PaymentSuccess

    PaymentSuccess -->|No| RetryPayment{Retry?}
    RetryPayment -->|Yes| PaymentMethod
    RetryPayment -->|No| BlockExit[๐Ÿšซ Keep Barrier Closed]
    BlockExit --> End1([โŒ Exit Denied])

    PaymentSuccess -->|Yes| UpdateTicket[โœ… Mark Ticket as PAID]
    UpdateTicket --> ReleaseSpot[๐Ÿ…ฟ๏ธ Release Parking Spot<br/>Update State to AVAILABLE]

    ReleaseSpot --> UpdateDisplay[๐Ÿ“บ Update Display Board<br/>Increment Available Count]
    UpdateDisplay --> IssueReceipt[๐Ÿงพ Generate & Print Receipt]

    IssueReceipt --> LogTransaction[๐Ÿ“ Log Transaction in Audit]
    LogTransaction --> OpenExitBarrier[๐Ÿšง Open Exit Barrier]

    OpenExitBarrier --> VehicleExits[๐Ÿš— Vehicle Exits]
    VehicleExits --> CloseBarrier[๐Ÿšง Close Barrier After 10s]

    CloseBarrier --> End2([โœ… Exit Complete])

    style Start fill:#e3f2fd,stroke:#1565c0,stroke-width:3px
    style ScanTicket fill:#b2dfdb,stroke:#00695c,stroke-width:2px
    style TicketValid fill:#fff9c4,stroke:#f57f17,stroke-width:2px
    style LostTicket fill:#ffccbc,stroke:#d84315,stroke-width:2px
    style CalcDuration fill:#bbdefb,stroke:#1976d2,stroke-width:2px
    style CheckPeak fill:#e1bee7,stroke:#7b1fa2,stroke-width:2px
    style ApplyPricing fill:#c8e6c9,stroke:#388e3c,stroke-width:2px
    style EVCheck fill:#fff59d,stroke:#fbc02d,stroke-width:2px
    style AddChargingFee fill:#c5e1a5,stroke:#558b2f,stroke-width:2px
    style CalcTotal fill:#dcedc8,stroke:#689f38,stroke-width:2px
    style DisplayFee fill:#f3e5f5,stroke:#8e24aa,stroke-width:2px
    style PaymentMethod fill:#fff9c4,stroke:#f57f17,stroke-width:2px
    style ProcessCard fill:#b3e5fc,stroke:#0277bd,stroke-width:2px
    style ProcessMobile fill:#ce93d8,stroke:#6a1b9a,stroke-width:2px
    style ProcessCash fill:#a5d6a7,stroke:#388e3c,stroke-width:2px
    style PaymentSuccess fill:#fff59d,stroke:#f9a825,stroke-width:2px
    style UpdateTicket fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
    style ReleaseSpot fill:#bbdefb,stroke:#1565c0,stroke-width:2px
    style UpdateDisplay fill:#f8bbd0,stroke:#c2185b,stroke-width:2px
    style IssueReceipt fill:#dcedc8,stroke:#558b2f,stroke-width:2px
    style LogTransaction fill:#d1c4e9,stroke:#5e35b1,stroke-width:2px
    style OpenExitBarrier fill:#b2dfdb,stroke:#00796b,stroke-width:2px
    style VehicleExits fill:#a5d6a7,stroke:#388e3c,stroke-width:2px
    style End1 fill:#ef5350,stroke:#c62828,stroke-width:3px
    style End2 fill:#66bb6a,stroke:#2e7d32,stroke-width:3px
    style BlockExit fill:#ffcdd2,stroke:#d32f2f,stroke-width:2px

3. Reservation Creation and Management Flow

flowchart TD
    Start([๐Ÿ“ฑ Customer Opens App]) --> Login[๐Ÿ” Customer Login]
    Login --> SelectDateTime[๐Ÿ“… Select Reservation<br/>Date & Time Range]

    SelectDateTime --> SelectVehicle[๐Ÿš— Select Vehicle Type]
    SelectVehicle --> CheckAvail[๐Ÿ” Check Spot Availability<br/>for Selected Time]

    CheckAvail --> SpotsAvail{Spots<br/>Available?}

    SpotsAvail -->|No| DisplayNoSpots[โŒ Display No Availability]
    DisplayNoSpots --> SuggestAlt[๐Ÿ’ก Suggest Alternative<br/>Time Slots]
    SuggestAlt --> AcceptAlt{Accept<br/>Alternative?}
    AcceptAlt -->|Yes| SelectDateTime
    AcceptAlt -->|No| End1([๐Ÿšซ Reservation Cancelled])

    SpotsAvail -->|Yes| ReserveSpot[โœ… Reserve Spot<br/>Mark as RESERVED]
    ReserveSpot --> CalcResFee[๐Ÿ’ฐ Calculate Reservation Fee]

    CalcResFee --> ProcessPayment[๐Ÿ’ณ Process Advance Payment]
    ProcessPayment --> PaymentOK{Payment<br/>Success?}

    PaymentOK -->|No| ReleaseSpot[โŒ Release Reserved Spot]
    ReleaseSpot --> End2([๐Ÿšซ Reservation Failed])

    PaymentOK -->|Yes| GenerateResID[๐ŸŽซ Generate Reservation ID]
    GenerateResID --> SendConfirm[๐Ÿ“ง Send Confirmation Email/SMS<br/>with QR Code]

    SendConfirm --> SetExpiry[โฐ Set Reservation Expiry<br/>15 min grace period]
    SetExpiry --> AddToCalendar[๐Ÿ“† Add to Customer Calendar]

    AddToCalendar --> End3([โœ… Reservation Confirmed])

    %% Expiry Check Flow
    SetExpiry -.->|Background Process| MonitorExpiry[โฑ๏ธ Monitor Reservation Time]
    MonitorExpiry --> TimeCheck{Customer<br/>Arrived?}
    TimeCheck -->|Yes| ValidateEntry[โœ… Validate at Entry Gate]
    ValidateEntry --> ConvertToTicket[๐ŸŽซ Convert to Active Ticket]
    ConvertToTicket --> End4([โœ… Checked In])

    TimeCheck -->|No, Expired| AutoCancel[โš ๏ธ Auto-Cancel Reservation]
    AutoCancel --> RefundFee[๐Ÿ’ธ Process Refund<br/>Per Cancellation Policy]
    RefundFee --> ReleaseSpot2[๐Ÿ…ฟ๏ธ Release Spot]
    ReleaseSpot2 --> NotifyCustomer[๐Ÿ“ง Send Cancellation Notice]
    NotifyCustomer --> End5([โŒ Reservation Expired])

    style Start fill:#e3f2fd,stroke:#1565c0,stroke-width:3px
    style Login fill:#b2dfdb,stroke:#00695c,stroke-width:2px
    style SelectDateTime fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style SelectVehicle fill:#fff9c4,stroke:#f57f17,stroke-width:2px
    style CheckAvail fill:#bbdefb,stroke:#1976d2,stroke-width:2px
    style SpotsAvail fill:#fff59d,stroke:#f9a825,stroke-width:2px
    style DisplayNoSpots fill:#ffccbc,stroke:#d84315,stroke-width:2px
    style SuggestAlt fill:#e1bee7,stroke:#8e24aa,stroke-width:2px
    style ReserveSpot fill:#c8e6c9,stroke:#388e3c,stroke-width:2px
    style CalcResFee fill:#dcedc8,stroke:#558b2f,stroke-width:2px
    style ProcessPayment fill:#b3e5fc,stroke:#0277bd,stroke-width:2px
    style PaymentOK fill:#fff59d,stroke:#fbc02d,stroke-width:2px
    style GenerateResID fill:#c5cae9,stroke:#3949ab,stroke-width:2px
    style SendConfirm fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
    style SetExpiry fill:#ffe082,stroke:#f9a825,stroke-width:2px
    style AddToCalendar fill:#f8bbd0,stroke:#c2185b,stroke-width:2px
    style MonitorExpiry fill:#d1c4e9,stroke:#5e35b1,stroke-width:2px
    style AutoCancel fill:#ffab91,stroke:#d84315,stroke-width:2px
    style RefundFee fill:#fff59d,stroke:#fbc02d,stroke-width:2px
    style End1 fill:#ef5350,stroke:#c62828,stroke-width:3px
    style End2 fill:#ef5350,stroke:#c62828,stroke-width:3px
    style End3 fill:#66bb6a,stroke:#2e7d32,stroke-width:3px
    style End4 fill:#66bb6a,stroke:#2e7d32,stroke-width:3px
    style End5 fill:#ff8a65,stroke:#d84315,stroke-width:3px

Implementation

Java Implementation

package parkinglot;

import java.time.*;
import java.util.*;
import java.util.concurrent.*;
import java.math.BigDecimal;

// ============== Enumerations ==============

enum VehicleType {
    MOTORCYCLE, CAR, TRUCK, ELECTRIC_CAR, VAN
}

enum SpotType {
    MOTORCYCLE, COMPACT, STANDARD, LARGE, ELECTRIC
}

enum SpotState {
    AVAILABLE, OCCUPIED, RESERVED, MAINTENANCE, OUT_OF_SERVICE
}

enum TicketStatus {
    ACTIVE, PAID, LOST, RESERVED
}

enum PaymentMethod {
    CASH, CREDIT_CARD, DEBIT_CARD, MOBILE_PAYMENT
}

enum PaymentStatus {
    PENDING, COMPLETED, FAILED, REFUNDED
}

enum ReservationStatus {
    CONFIRMED, ACTIVE, COMPLETED, CANCELLED, EXPIRED
}

// ============== Value Objects ==============

class Money {
    private final BigDecimal amount;
    private final String currency;

    public Money(double amount) {
        this(BigDecimal.valueOf(amount), "USD");
    }

    public Money(BigDecimal amount, String currency) {
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("Amount cannot be negative");
        }
        this.amount = amount;
        this.currency = currency;
    }

    public Money add(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new IllegalArgumentException("Currency mismatch");
        }
        return new Money(this.amount.add(other.amount), this.currency);
    }

    public Money multiply(double multiplier) {
        return new Money(this.amount.multiply(BigDecimal.valueOf(multiplier)), this.currency);
    }

    public BigDecimal getAmount() {
        return amount;
    }

    public String getCurrency() {
        return currency;
    }

    @Override
    public String toString() {
        return currency + " " + amount.setScale(2, BigDecimal.ROUND_HALF_UP);
    }
}

class Address {
    private final String street;
    private final String city;
    private final String state;
    private final String zipCode;
    private final String country;

    public Address(String street, String city, String state, String zipCode, String country) {
        this.street = street;
        this.city = city;
        this.state = state;
        this.zipCode = zipCode;
        this.country = country;
    }

    public String getFullAddress() {
        return street + ", " + city + ", " + state + " " + zipCode + ", " + country;
    }
}

// ============== Vehicle Hierarchy ==============

abstract class Vehicle {
    protected final String licensePlate;
    protected final VehicleType type;

    public Vehicle(String licensePlate, VehicleType type) {
        this.licensePlate = licensePlate;
        this.type = type;
    }

    public String getLicensePlate() {
        return licensePlate;
    }

    public VehicleType getType() {
        return type;
    }
}

class Motorcycle extends Vehicle {
    public Motorcycle(String licensePlate) {
        super(licensePlate, VehicleType.MOTORCYCLE);
    }
}

class Car extends Vehicle {
    public Car(String licensePlate) {
        super(licensePlate, VehicleType.CAR);
    }
}

class Truck extends Vehicle {
    public Truck(String licensePlate) {
        super(licensePlate, VehicleType.TRUCK);
    }
}

class ElectricCar extends Vehicle {
    private int batteryCapacity; // in kWh
    private int currentCharge; // percentage

    public ElectricCar(String licensePlate, int batteryCapacity) {
        super(licensePlate, VehicleType.ELECTRIC_CAR);
        this.batteryCapacity = batteryCapacity;
        this.currentCharge = 50; // assume 50% when entering
    }

    public boolean needsCharging() {
        return currentCharge < 80;
    }

    public int getBatteryCapacity() {
        return batteryCapacity;
    }

    public void setCurrentCharge(int charge) {
        this.currentCharge = Math.min(100, Math.max(0, charge));
    }
}

// ============== Parking Spot Hierarchy ==============

abstract class ParkingSpot {
    protected final String spotId;
    protected final SpotType spotType;
    protected SpotState state;
    protected Vehicle vehicle;
    protected final int floorNumber;

    public ParkingSpot(String spotId, SpotType spotType, int floorNumber) {
        this.spotId = spotId;
        this.spotType = spotType;
        this.floorNumber = floorNumber;
        this.state = SpotState.AVAILABLE;
    }

    public abstract boolean canFitVehicle(Vehicle vehicle);

    public synchronized boolean isAvailable() {
        return state == SpotState.AVAILABLE;
    }

    public synchronized void assignVehicle(Vehicle vehicle) {
        if (!isAvailable()) {
            throw new IllegalStateException("Spot is not available");
        }
        if (!canFitVehicle(vehicle)) {
            throw new IllegalArgumentException("Vehicle cannot fit in this spot");
        }
        this.vehicle = vehicle;
        this.state = SpotState.OCCUPIED;
    }

    public synchronized void releaseVehicle() {
        this.vehicle = null;
        this.state = SpotState.AVAILABLE;
    }

    public synchronized void reserve() {
        if (state != SpotState.AVAILABLE) {
            throw new IllegalStateException("Cannot reserve non-available spot");
        }
        this.state = SpotState.RESERVED;
    }

    public void setState(SpotState state) {
        this.state = state;
    }

    public String getSpotId() {
        return spotId;
    }

    public SpotType getSpotType() {
        return spotType;
    }

    public SpotState getState() {
        return state;
    }

    public Vehicle getVehicle() {
        return vehicle;
    }

    public int getFloorNumber() {
        return floorNumber;
    }
}

class CompactSpot extends ParkingSpot {
    public CompactSpot(String spotId, int floorNumber) {
        super(spotId, SpotType.COMPACT, floorNumber);
    }

    @Override
    public boolean canFitVehicle(Vehicle vehicle) {
        return vehicle.getType() == VehicleType.CAR ||
               vehicle.getType() == VehicleType.MOTORCYCLE;
    }
}

class StandardSpot extends ParkingSpot {
    public StandardSpot(String spotId, int floorNumber) {
        super(spotId, SpotType.STANDARD, floorNumber);
    }

    @Override
    public boolean canFitVehicle(Vehicle vehicle) {
        return vehicle.getType() == VehicleType.CAR ||
               vehicle.getType() == VehicleType.MOTORCYCLE ||
               vehicle.getType() == VehicleType.VAN;
    }
}

class LargeSpot extends ParkingSpot {
    public LargeSpot(String spotId, int floorNumber) {
        super(spotId, SpotType.LARGE, floorNumber);
    }

    @Override
    public boolean canFitVehicle(Vehicle vehicle) {
        return true; // Can fit any vehicle
    }
}

class MotorcycleSpot extends ParkingSpot {
    public MotorcycleSpot(String spotId, int floorNumber) {
        super(spotId, SpotType.MOTORCYCLE, floorNumber);
    }

    @Override
    public boolean canFitVehicle(Vehicle vehicle) {
        return vehicle.getType() == VehicleType.MOTORCYCLE;
    }
}

class ElectricSpot extends ParkingSpot {
    private ChargingStation chargingStation;

    public ElectricSpot(String spotId, int floorNumber) {
        super(spotId, SpotType.ELECTRIC, floorNumber);
        this.chargingStation = new ChargingStation(spotId + "-CHARGER");
    }

    @Override
    public boolean canFitVehicle(Vehicle vehicle) {
        return vehicle.getType() == VehicleType.ELECTRIC_CAR;
    }

    public void startCharging(ElectricCar car) {
        chargingStation.startCharging(car);
    }

    public void stopCharging() {
        chargingStation.stopCharging();
    }

    public Money getChargingFee() {
        return chargingStation.calculateChargingFee();
    }

    public ChargingStation getChargingStation() {
        return chargingStation;
    }
}

// ============== Charging Station ==============

class ChargingStation {
    private final String stationId;
    private boolean isCharging;
    private LocalDateTime chargingStartTime;
    private int kWhCharged;
    private static final Money RATE_PER_KWH = new Money(0.30); // $0.30 per kWh

    public ChargingStation(String stationId) {
        this.stationId = stationId;
        this.isCharging = false;
        this.kWhCharged = 0;
    }

    public void startCharging(ElectricCar car) {
        this.isCharging = true;
        this.chargingStartTime = LocalDateTime.now();
        // Simulate charging based on time
    }

    public void stopCharging() {
        if (isCharging) {
            Duration duration = Duration.between(chargingStartTime, LocalDateTime.now());
            // Assume 7 kW charger
            this.kWhCharged = (int) (duration.toHours() * 7);
            this.isCharging = false;
        }
    }

    public Money calculateChargingFee() {
        return RATE_PER_KWH.multiply(kWhCharged);
    }

    public String getStationId() {
        return stationId;
    }

    public int getKWhCharged() {
        return kWhCharged;
    }
}

// ============== Ticket ==============

class Ticket {
    private final String ticketId;
    private final LocalDateTime issueTime;
    private ParkingSpot assignedSpot;
    private final Vehicle vehicle;
    private TicketStatus status;
    private LocalDateTime paidTime;

    public Ticket(Vehicle vehicle, ParkingSpot spot) {
        this.ticketId = UUID.randomUUID().toString();
        this.issueTime = LocalDateTime.now();
        this.assignedSpot = spot;
        this.vehicle = vehicle;
        this.status = TicketStatus.ACTIVE;
    }

    public Duration getParkedDuration() {
        LocalDateTime endTime = (paidTime != null) ? paidTime : LocalDateTime.now();
        return Duration.between(issueTime, endTime);
    }

    public void markPaid() {
        this.status = TicketStatus.PAID;
        this.paidTime = LocalDateTime.now();
    }

    public void markLost() {
        this.status = TicketStatus.LOST;
    }

    // Getters
    public String getTicketId() { return ticketId; }
    public LocalDateTime getIssueTime() { return issueTime; }
    public ParkingSpot getAssignedSpot() { return assignedSpot; }
    public Vehicle getVehicle() { return vehicle; }
    public TicketStatus getStatus() { return status; }
    public LocalDateTime getPaidTime() { return paidTime; }
}

// ============== Pricing Strategy Pattern ==============

interface PricingStrategy {
    Money calculateFee(Ticket ticket);
}

class HourlyPricing implements PricingStrategy {
    private Map<VehicleType, Money> hourlyRates;
    private Money maxDailyRate;

    public HourlyPricing() {
        this.hourlyRates = new EnumMap<>(VehicleType.class);
        hourlyRates.put(VehicleType.MOTORCYCLE, new Money(2.0));
        hourlyRates.put(VehicleType.CAR, new Money(5.0));
        hourlyRates.put(VehicleType.ELECTRIC_CAR, new Money(5.0));
        hourlyRates.put(VehicleType.TRUCK, new Money(10.0));
        hourlyRates.put(VehicleType.VAN, new Money(7.0));
        this.maxDailyRate = new Money(50.0);
    }

    @Override
    public Money calculateFee(Ticket ticket) {
        Duration duration = ticket.getParkedDuration();
        long hours = duration.toHours();
        if (duration.toMinutes() % 60 > 0) {
            hours++; // Round up partial hours
        }

        Money hourlyRate = hourlyRates.get(ticket.getVehicle().getType());
        Money totalFee = hourlyRate.multiply(hours);

        // Apply daily cap
        if (totalFee.getAmount().compareTo(maxDailyRate.getAmount()) > 0) {
            return maxDailyRate;
        }

        return totalFee;
    }

    public void updateRate(VehicleType type, Money newRate) {
        hourlyRates.put(type, newRate);
    }
}

class PeakHourPricing implements PricingStrategy {
    private final PricingStrategy baseStrategy;
    private final double peakMultiplier;
    private final LocalTime peakStartMorning = LocalTime.of(7, 0);
    private final LocalTime peakEndMorning = LocalTime.of(10, 0);
    private final LocalTime peakStartEvening = LocalTime.of(16, 0);
    private final LocalTime peakEndEvening = LocalTime.of(19, 0);

    public PeakHourPricing(PricingStrategy baseStrategy, double peakMultiplier) {
        this.baseStrategy = baseStrategy;
        this.peakMultiplier = peakMultiplier;
    }

    private boolean isPeakHour(LocalDateTime time) {
        LocalTime timeOfDay = time.toLocalTime();
        return (timeOfDay.isAfter(peakStartMorning) && timeOfDay.isBefore(peakEndMorning)) ||
               (timeOfDay.isAfter(peakStartEvening) && timeOfDay.isBefore(peakEndEvening));
    }

    @Override
    public Money calculateFee(Ticket ticket) {
        Money baseFee = baseStrategy.calculateFee(ticket);

        // Check if parked during peak hours
        if (isPeakHour(ticket.getIssueTime()) ||
            (ticket.getPaidTime() != null && isPeakHour(ticket.getPaidTime()))) {
            return baseFee.multiply(peakMultiplier);
        }

        return baseFee;
    }
}

class MonthlyPassPricing implements PricingStrategy {
    private Map<String, MonthlyPass> activePasses;
    private PricingStrategy fallbackStrategy;

    public MonthlyPassPricing(PricingStrategy fallbackStrategy) {
        this.activePasses = new ConcurrentHashMap<>();
        this.fallbackStrategy = fallbackStrategy;
    }

    @Override
    public Money calculateFee(Ticket ticket) {
        String licensePlate = ticket.getVehicle().getLicensePlate();
        MonthlyPass pass = activePasses.get(licensePlate);

        if (pass != null && pass.isValid()) {
            return new Money(0.0); // Free for monthly pass holders
        }

        return fallbackStrategy.calculateFee(ticket);
    }

    public void addPass(MonthlyPass pass) {
        activePasses.put(pass.getLicensePlate(), pass);
    }

    public void removePass(String licensePlate) {
        activePasses.remove(licensePlate);
    }
}

class MonthlyPass {
    private final String passId;
    private final String licensePlate;
    private final LocalDate startDate;
    private final LocalDate endDate;

    public MonthlyPass(String licensePlate, LocalDate startDate) {
        this.passId = UUID.randomUUID().toString();
        this.licensePlate = licensePlate;
        this.startDate = startDate;
        this.endDate = startDate.plusMonths(1);
    }

    public boolean isValid() {
        LocalDate today = LocalDate.now();
        return !today.isBefore(startDate) && !today.isAfter(endDate);
    }

    public String getLicensePlate() {
        return licensePlate;
    }
}

// ============== Payment ==============

class Payment {
    private final String paymentId;
    private final Money amount;
    private final PaymentMethod method;
    private PaymentStatus status;
    private final LocalDateTime timestamp;
    private String transactionRef;

    public Payment(Money amount, PaymentMethod method) {
        this.paymentId = UUID.randomUUID().toString();
        this.amount = amount;
        this.method = method;
        this.status = PaymentStatus.PENDING;
        this.timestamp = LocalDateTime.now();
    }

    public boolean process() {
        // Simulate payment processing
        try {
            Thread.sleep(100); // Simulate network delay

            // 95% success rate for simulation
            if (Math.random() < 0.95) {
                this.status = PaymentStatus.COMPLETED;
                this.transactionRef = "TXN-" + UUID.randomUUID().toString().substring(0, 8);
                return true;
            } else {
                this.status = PaymentStatus.FAILED;
                return false;
            }
        } catch (InterruptedException e) {
            this.status = PaymentStatus.FAILED;
            return false;
        }
    }

    public void refund() {
        if (status == PaymentStatus.COMPLETED) {
            this.status = PaymentStatus.REFUNDED;
        }
    }

    // Getters
    public String getPaymentId() { return paymentId; }
    public Money getAmount() { return amount; }
    public PaymentMethod getMethod() { return method; }
    public PaymentStatus getStatus() { return status; }
    public String getTransactionRef() { return transactionRef; }
}

class Receipt {
    private final String receiptId;
    private final Ticket ticket;
    private final Payment payment;
    private final Money parkingFee;
    private final Money chargingFee;
    private final Money totalFee;
    private final LocalDateTime issueTime;

    public Receipt(Ticket ticket, Payment payment, Money parkingFee, Money chargingFee) {
        this.receiptId = UUID.randomUUID().toString();
        this.ticket = ticket;
        this.payment = payment;
        this.parkingFee = parkingFee;
        this.chargingFee = chargingFee;
        this.totalFee = parkingFee.add(chargingFee);
        this.issueTime = LocalDateTime.now();
    }

    public String generateReceipt() {
        StringBuilder sb = new StringBuilder();
        sb.append("========== PARKING RECEIPT ==========\n");
        sb.append("Receipt ID: ").append(receiptId).append("\n");
        sb.append("Ticket ID: ").append(ticket.getTicketId()).append("\n");
        sb.append("Vehicle: ").append(ticket.getVehicle().getLicensePlate()).append("\n");
        sb.append("Entry Time: ").append(ticket.getIssueTime()).append("\n");
        sb.append("Exit Time: ").append(ticket.getPaidTime()).append("\n");
        sb.append("Duration: ").append(ticket.getParkedDuration().toHours()).append(" hours\n");
        sb.append("Parking Fee: ").append(parkingFee).append("\n");
        if (chargingFee.getAmount().compareTo(BigDecimal.ZERO) > 0) {
            sb.append("Charging Fee: ").append(chargingFee).append("\n");
        }
        sb.append("Total: ").append(totalFee).append("\n");
        sb.append("Payment Method: ").append(payment.getMethod()).append("\n");
        sb.append("Transaction Ref: ").append(payment.getTransactionRef()).append("\n");
        sb.append("====================================\n");
        return sb.toString();
    }
}

// ============== Display Board (Observer Pattern) ==============

class DisplayBoard {
    private final int floorNumber;
    private Map<SpotType, Integer> availableCounts;

    public DisplayBoard(int floorNumber) {
        this.floorNumber = floorNumber;
        this.availableCounts = new EnumMap<>(SpotType.class);
        for (SpotType type : SpotType.values()) {
            availableCounts.put(type, 0);
        }
    }

    public synchronized void update(SpotType spotType, int availableCount) {
        availableCounts.put(spotType, availableCount);
    }

    public void displayAvailability() {
        System.out.println("========== FLOOR " + floorNumber + " ==========");
        for (Map.Entry<SpotType, Integer> entry : availableCounts.entrySet()) {
            String icon = getIconForSpotType(entry.getKey());
            System.out.println(icon + " " + entry.getKey() + ": " + entry.getValue() + " available");
        }
        System.out.println("================================");
    }

    private String getIconForSpotType(SpotType type) {
        switch (type) {
            case MOTORCYCLE: return "๐Ÿ๏ธ";
            case COMPACT: return "๐Ÿš—";
            case STANDARD: return "๐Ÿš™";
            case LARGE: return "๐Ÿš";
            case ELECTRIC: return "โšก";
            default: return "๐Ÿ…ฟ๏ธ";
        }
    }
}

// ============== Parking Floor ==============

class ParkingFloor {
    private final int floorNumber;
    private Map<SpotType, List<ParkingSpot>> spotsByType;
    private DisplayBoard displayBoard;

    public ParkingFloor(int floorNumber) {
        this.floorNumber = floorNumber;
        this.spotsByType = new EnumMap<>(SpotType.class);
        for (SpotType type : SpotType.values()) {
            spotsByType.put(type, new CopyOnWriteArrayList<>());
        }
        this.displayBoard = new DisplayBoard(floorNumber);
    }

    public void addSpot(ParkingSpot spot) {
        spotsByType.get(spot.getSpotType()).add(spot);
        updateDisplayBoard();
    }

    public void removeSpot(String spotId) {
        for (List<ParkingSpot> spots : spotsByType.values()) {
            spots.removeIf(spot -> spot.getSpotId().equals(spotId));
        }
        updateDisplayBoard();
    }

    public synchronized ParkingSpot findAvailableSpot(VehicleType vehicleType) {
        // Priority order for finding spots
        List<SpotType> preferredTypes = getPreferredSpotTypes(vehicleType);

        for (SpotType spotType : preferredTypes) {
            List<ParkingSpot> spots = spotsByType.get(spotType);
            for (ParkingSpot spot : spots) {
                if (spot.isAvailable() && spot.canFitVehicle(new Car("TEMP"))) {
                    return spot;
                }
            }
        }

        return null; // No available spot
    }

    private List<SpotType> getPreferredSpotTypes(VehicleType vehicleType) {
        List<SpotType> preferred = new ArrayList<>();
        switch (vehicleType) {
            case MOTORCYCLE:
                preferred.add(SpotType.MOTORCYCLE);
                preferred.add(SpotType.COMPACT);
                break;
            case CAR:
                preferred.add(SpotType.COMPACT);
                preferred.add(SpotType.STANDARD);
                break;
            case ELECTRIC_CAR:
                preferred.add(SpotType.ELECTRIC);
                preferred.add(SpotType.STANDARD);
                break;
            case VAN:
                preferred.add(SpotType.STANDARD);
                preferred.add(SpotType.LARGE);
                break;
            case TRUCK:
                preferred.add(SpotType.LARGE);
                break;
        }
        return preferred;
    }

    public int getAvailableCount(SpotType spotType) {
        return (int) spotsByType.get(spotType).stream()
            .filter(ParkingSpot::isAvailable)
            .count();
    }

    public void updateDisplayBoard() {
        for (SpotType type : SpotType.values()) {
            int available = getAvailableCount(type);
            displayBoard.update(type, available);
        }
    }

    public int getFloorNumber() {
        return floorNumber;
    }

    public DisplayBoard getDisplayBoard() {
        return displayBoard;
    }
}

// ============== Reservation ==============

class Reservation {
    private final String reservationId;
    private final Vehicle vehicle;
    private final LocalDateTime startTime;
    private final LocalDateTime endTime;
    private ParkingSpot reservedSpot;
    private ReservationStatus status;

    public Reservation(Vehicle vehicle, LocalDateTime startTime, LocalDateTime endTime, ParkingSpot spot) {
        this.reservationId = UUID.randomUUID().toString();
        this.vehicle = vehicle;
        this.startTime = startTime;
        this.endTime = endTime;
        this.reservedSpot = spot;
        this.status = ReservationStatus.CONFIRMED;
    }

    public boolean isValid() {
        LocalDateTime now = LocalDateTime.now();
        return status == ReservationStatus.CONFIRMED &&
               !now.isBefore(startTime) &&
               !now.isAfter(endTime.plusMinutes(15)); // 15 min grace period
    }

    public boolean isExpired() {
        return LocalDateTime.now().isAfter(endTime.plusMinutes(15));
    }

    public void cancel() {
        this.status = ReservationStatus.CANCELLED;
        if (reservedSpot != null) {
            reservedSpot.setState(SpotState.AVAILABLE);
        }
    }

    public void activate() {
        this.status = ReservationStatus.ACTIVE;
    }

    public void complete() {
        this.status = ReservationStatus.COMPLETED;
    }

    // Getters
    public String getReservationId() { return reservationId; }
    public Vehicle getVehicle() { return vehicle; }
    public ParkingSpot getReservedSpot() { return reservedSpot; }
    public ReservationStatus getStatus() { return status; }
    public LocalDateTime getStartTime() { return startTime; }
    public LocalDateTime getEndTime() { return endTime; }
}

class ReservationManager {
    private Map<String, Reservation> reservations;

    public ReservationManager() {
        this.reservations = new ConcurrentHashMap<>();
        startExpiryMonitor();
    }

    public Reservation createReservation(Vehicle vehicle, LocalDateTime startTime,
                                        LocalDateTime endTime, ParkingSpot spot) {
        spot.reserve();
        Reservation reservation = new Reservation(vehicle, startTime, endTime, spot);
        reservations.put(reservation.getReservationId(), reservation);
        return reservation;
    }

    public boolean validateReservation(String reservationId) {
        Reservation reservation = reservations.get(reservationId);
        return reservation != null && reservation.isValid();
    }

    public Reservation getReservation(String reservationId) {
        return reservations.get(reservationId);
    }

    public void cancelReservation(String reservationId) {
        Reservation reservation = reservations.get(reservationId);
        if (reservation != null) {
            reservation.cancel();
        }
    }

    private void startExpiryMonitor() {
        // Background task to check for expired reservations
        Thread monitor = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(60000); // Check every minute
                    LocalDateTime now = LocalDateTime.now();

                    for (Reservation reservation : reservations.values()) {
                        if (reservation.isExpired() &&
                            reservation.getStatus() == ReservationStatus.CONFIRMED) {
                            reservation.cancel();
                            System.out.println("Reservation " + reservation.getReservationId() +
                                             " expired and cancelled");
                        }
                    }
                } catch (InterruptedException e) {
                    break;
                }
            }
        });
        monitor.setDaemon(true);
        monitor.start();
    }
}

// ============== Entry and Exit Gates ==============

class EntranceGate {
    private final String gateId;
    private final ParkingLot parkingLot;

    public EntranceGate(String gateId, ParkingLot parkingLot) {
        this.gateId = gateId;
        this.parkingLot = parkingLot;
    }

    public Ticket issueTicket(Vehicle vehicle, String reservationId) {
        // Check reservation first
        if (reservationId != null) {
            if (!parkingLot.getReservationManager().validateReservation(reservationId)) {
                throw new IllegalStateException("Invalid or expired reservation");
            }
            Reservation reservation = parkingLot.getReservationManager().getReservation(reservationId);
            reservation.activate();

            Ticket ticket = new Ticket(vehicle, reservation.getReservedSpot());
            reservation.getReservedSpot().assignVehicle(vehicle);
            return ticket;
        }

        // Find available spot
        ParkingSpot spot = parkingLot.getAvailableSpot(vehicle.getType());
        if (spot == null) {
            throw new IllegalStateException("No available spots for vehicle type: " + vehicle.getType());
        }

        spot.assignVehicle(vehicle);
        Ticket ticket = new Ticket(vehicle, spot);
        parkingLot.addActiveTicket(ticket);

        // Update display boards
        parkingLot.updateAllDisplayBoards();

        System.out.println("โœ… Ticket issued: " + ticket.getTicketId() +
                         " | Spot: " + spot.getSpotId() +
                         " | Floor: " + spot.getFloorNumber());

        return ticket;
    }

    public String getGateId() {
        return gateId;
    }
}

class ExitGate {
    private final String gateId;
    private final ParkingLot parkingLot;

    public ExitGate(String gateId, ParkingLot parkingLot) {
        this.gateId = gateId;
        this.parkingLot = parkingLot;
    }

    public Receipt processExit(String ticketId, PaymentMethod paymentMethod) {
        Ticket ticket = parkingLot.getActiveTicket(ticketId);
        if (ticket == null) {
            throw new IllegalArgumentException("Invalid ticket ID");
        }

        // Calculate parking fee
        Money parkingFee = parkingLot.getPricingStrategy().calculateFee(ticket);

        // Calculate charging fee if applicable
        Money chargingFee = new Money(0.0);
        ParkingSpot spot = ticket.getAssignedSpot();
        if (spot instanceof ElectricSpot) {
            ElectricSpot electricSpot = (ElectricSpot) spot;
            electricSpot.stopCharging();
            chargingFee = electricSpot.getChargingFee();
        }

        Money totalFee = parkingFee.add(chargingFee);

        // Process payment
        Payment payment = new Payment(totalFee, paymentMethod);
        boolean paymentSuccess = payment.process();

        if (!paymentSuccess) {
            throw new IllegalStateException("Payment failed");
        }

        // Mark ticket as paid and release spot
        ticket.markPaid();
        spot.releaseVehicle();
        parkingLot.removeActiveTicket(ticketId);

        // Update display boards
        parkingLot.updateAllDisplayBoards();

        // Generate receipt
        Receipt receipt = new Receipt(ticket, payment, parkingFee, chargingFee);

        System.out.println("โœ… Exit processed successfully");
        System.out.println(receipt.generateReceipt());

        return receipt;
    }

    public String getGateId() {
        return gateId;
    }
}

// ============== Parking Lot (Singleton) ==============

class ParkingLot {
    private static ParkingLot instance;
    private final String name;
    private final Address address;
    private List<ParkingFloor> floors;
    private List<EntranceGate> entranceGates;
    private List<ExitGate> exitGates;
    private PricingStrategy pricingStrategy;
    private ReservationManager reservationManager;
    private Map<String, Ticket> activeTickets;

    private ParkingLot(String name, Address address) {
        this.name = name;
        this.address = address;
        this.floors = new CopyOnWriteArrayList<>();
        this.entranceGates = new CopyOnWriteArrayList<>();
        this.exitGates = new CopyOnWriteArrayList<>();
        this.pricingStrategy = new HourlyPricing();
        this.reservationManager = new ReservationManager();
        this.activeTickets = new ConcurrentHashMap<>();
    }

    public static synchronized ParkingLot getInstance(String name, Address address) {
        if (instance == null) {
            instance = new ParkingLot(name, address);
        }
        return instance;
    }

    public static ParkingLot getInstance() {
        if (instance == null) {
            throw new IllegalStateException("ParkingLot not initialized");
        }
        return instance;
    }

    public void addFloor(ParkingFloor floor) {
        floors.add(floor);
    }

    public void addEntranceGate(EntranceGate gate) {
        entranceGates.add(gate);
    }

    public void addExitGate(ExitGate gate) {
        exitGates.add(gate);
    }

    public synchronized ParkingSpot getAvailableSpot(VehicleType vehicleType) {
        for (ParkingFloor floor : floors) {
            ParkingSpot spot = floor.findAvailableSpot(vehicleType);
            if (spot != null) {
                return spot;
            }
        }
        return null;
    }

    public int getTotalCapacity() {
        int total = 0;
        for (ParkingFloor floor : floors) {
            for (SpotType type : SpotType.values()) {
                total += floor.getAvailableCount(type);
            }
        }
        return total;
    }

    public int getAvailableCount(SpotType spotType) {
        return floors.stream()
            .mapToInt(floor -> floor.getAvailableCount(spotType))
            .sum();
    }

    public void updateAllDisplayBoards() {
        for (ParkingFloor floor : floors) {
            floor.updateDisplayBoard();
        }
    }

    public void displayAllFloors() {
        for (ParkingFloor floor : floors) {
            floor.getDisplayBoard().displayAvailability();
        }
    }

    public void addActiveTicket(Ticket ticket) {
        activeTickets.put(ticket.getTicketId(), ticket);
    }

    public void removeActiveTicket(String ticketId) {
        activeTickets.remove(ticketId);
    }

    public Ticket getActiveTicket(String ticketId) {
        return activeTickets.get(ticketId);
    }

    public void setPricingStrategy(PricingStrategy strategy) {
        this.pricingStrategy = strategy;
    }

    public PricingStrategy getPricingStrategy() {
        return pricingStrategy;
    }

    public ReservationManager getReservationManager() {
        return reservationManager;
    }

    public List<EntranceGate> getEntranceGates() {
        return entranceGates;
    }

    public List<ExitGate> getExitGates() {
        return exitGates;
    }
}

// ============== Admin and Attendant ==============

class Admin {
    private final String adminId;
    private final String name;

    public Admin(String adminId, String name) {
        this.adminId = adminId;
        this.name = name;
    }

    public void addFloor(ParkingLot parkingLot, ParkingFloor floor) {
        parkingLot.addFloor(floor);
        System.out.println("Admin " + name + " added floor " + floor.getFloorNumber());
    }

    public void addSpot(ParkingFloor floor, ParkingSpot spot) {
        floor.addSpot(spot);
        System.out.println("Admin " + name + " added spot " + spot.getSpotId());
    }

    public void updatePricing(ParkingLot parkingLot, PricingStrategy strategy) {
        parkingLot.setPricingStrategy(strategy);
        System.out.println("Admin " + name + " updated pricing strategy");
    }

    public void markSpotMaintenance(ParkingSpot spot) {
        spot.setState(SpotState.MAINTENANCE);
        System.out.println("Admin " + name + " marked spot " + spot.getSpotId() + " for maintenance");
    }
}

class ParkingAttendant {
    private final String attendantId;
    private final String name;

    public ParkingAttendant(String attendantId, String name) {
        this.attendantId = attendantId;
        this.name = name;
    }

    public Receipt assistPayment(ExitGate exitGate, String ticketId, PaymentMethod method) {
        System.out.println("Attendant " + name + " assisting with payment");
        return exitGate.processExit(ticketId, method);
    }

    public Ticket handleLostTicket(ParkingLot parkingLot, Vehicle vehicle) {
        System.out.println("Attendant " + name + " handling lost ticket for " + vehicle.getLicensePlate());

        // Create a lost ticket with maximum fee assumption (24 hours)
        ParkingSpot tempSpot = parkingLot.getAvailableSpot(vehicle.getType());
        Ticket lostTicket = new Ticket(vehicle, tempSpot);
        lostTicket.markLost();

        return lostTicket;
    }
}

// ============== Demo Class ==============

class ParkingLotDemo {
    public static void main(String[] args) {
        System.out.println("๐Ÿ…ฟ๏ธ  Initializing Parking Lot System...\n");

        // Create parking lot
        Address address = new Address("123 Main St", "San Francisco", "CA", "94101", "USA");
        ParkingLot parkingLot = ParkingLot.getInstance("Downtown Parking", address);

        // Create floors
        ParkingFloor floor1 = new ParkingFloor(1);
        ParkingFloor floor2 = new ParkingFloor(2);

        // Add spots to floor 1
        for (int i = 1; i <= 5; i++) {
            floor1.addSpot(new CompactSpot("F1-C" + i, 1));
        }
        for (int i = 1; i <= 5; i++) {
            floor1.addSpot(new StandardSpot("F1-S" + i, 1));
        }
        for (int i = 1; i <= 3; i++) {
            floor1.addSpot(new MotorcycleSpot("F1-M" + i, 1));
        }
        for (int i = 1; i <= 2; i++) {
            floor1.addSpot(new ElectricSpot("F1-E" + i, 1));
        }

        // Add spots to floor 2
        for (int i = 1; i <= 5; i++) {
            floor2.addSpot(new StandardSpot("F2-S" + i, 2));
        }
        for (int i = 1; i <= 3; i++) {
            floor2.addSpot(new LargeSpot("F2-L" + i, 2));
        }

        // Add floors to parking lot
        parkingLot.addFloor(floor1);
        parkingLot.addFloor(floor2);

        // Create gates
        EntranceGate entrance1 = new EntranceGate("ENT-1", parkingLot);
        ExitGate exit1 = new ExitGate("EXIT-1", parkingLot);
        parkingLot.addEntranceGate(entrance1);
        parkingLot.addExitGate(exit1);

        // Display initial availability
        System.out.println("๐Ÿ“Š Initial Parking Availability:");
        parkingLot.displayAllFloors();
        System.out.println();

        // Scenario 1: Regular car entry and exit
        System.out.println("๐Ÿš— Scenario 1: Car Entry and Exit\n");
        Vehicle car1 = new Car("ABC-123");
        Ticket ticket1 = entrance1.issueTicket(car1, null);

        try {
            Thread.sleep(2000); // Simulate 2 seconds parking
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Receipt receipt1 = exit1.processExit(ticket1.getTicketId(), PaymentMethod.CREDIT_CARD);
        System.out.println();

        // Scenario 2: Electric car with charging
        System.out.println("โšก Scenario 2: Electric Car with Charging\n");
        ElectricCar electricCar = new ElectricCar("TESLA-X", 75);
        Ticket ticket2 = entrance1.issueTicket(electricCar, null);

        // Start charging
        if (ticket2.getAssignedSpot() instanceof ElectricSpot) {
            ElectricSpot electricSpot = (ElectricSpot) ticket2.getAssignedSpot();
            electricSpot.startCharging(electricCar);
            System.out.println("โšก Charging started at " + electricSpot.getSpotId());
        }

        try {
            Thread.sleep(3000); // Simulate 3 seconds charging
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Receipt receipt2 = exit1.processExit(ticket2.getTicketId(), PaymentMethod.MOBILE_PAYMENT);
        System.out.println();

        // Scenario 3: Reservation
        System.out.println("๐Ÿ“… Scenario 3: Advance Reservation\n");
        Vehicle car2 = new Car("XYZ-789");
        LocalDateTime reservationStart = LocalDateTime.now();
        LocalDateTime reservationEnd = reservationStart.plusHours(2);

        ParkingSpot spotForReservation = parkingLot.getAvailableSpot(VehicleType.CAR);
        Reservation reservation = parkingLot.getReservationManager()
            .createReservation(car2, reservationStart, reservationEnd, spotForReservation);

        System.out.println("โœ… Reservation created: " + reservation.getReservationId());
        System.out.println("   Spot: " + spotForReservation.getSpotId());
        System.out.println("   Valid until: " + reservationEnd.plusMinutes(15));
        System.out.println();

        // Use reservation
        Ticket ticket3 = entrance1.issueTicket(car2, reservation.getReservationId());
        System.out.println("โœ… Reservation activated, vehicle parked\n");

        // Display final availability
        System.out.println("๐Ÿ“Š Final Parking Availability:");
        parkingLot.displayAllFloors();

        System.out.println("\nโœ… Parking Lot System Demo Complete!");
    }
}

Python Implementation

from enum import Enum
from dataclasses import dataclass
from typing import List, Dict, Optional, Protocol
from datetime import datetime, timedelta
from abc import ABC, abstractmethod
import uuid
import time
import threading
from decimal import Decimal

# ============== Enumerations ==============

class VehicleType(Enum):
    MOTORCYCLE = "motorcycle"
    CAR = "car"
    TRUCK = "truck"
    ELECTRIC_CAR = "electric_car"
    VAN = "van"

class SpotType(Enum):
    MOTORCYCLE = "motorcycle"
    COMPACT = "compact"
    STANDARD = "standard"
    LARGE = "large"
    ELECTRIC = "electric"

class SpotState(Enum):
    AVAILABLE = "available"
    OCCUPIED = "occupied"
    RESERVED = "reserved"
    MAINTENANCE = "maintenance"
    OUT_OF_SERVICE = "out_of_service"

class TicketStatus(Enum):
    ACTIVE = "active"
    PAID = "paid"
    LOST = "lost"
    RESERVED = "reserved"

class PaymentMethod(Enum):
    CASH = "cash"
    CREDIT_CARD = "credit_card"
    DEBIT_CARD = "debit_card"
    MOBILE_PAYMENT = "mobile_payment"

class PaymentStatus(Enum):
    PENDING = "pending"
    COMPLETED = "completed"
    FAILED = "failed"
    REFUNDED = "refunded"

class ReservationStatus(Enum):
    CONFIRMED = "confirmed"
    ACTIVE = "active"
    COMPLETED = "completed"
    CANCELLED = "cancelled"
    EXPIRED = "expired"

# ============== Value Objects ==============

@dataclass(frozen=True)
class Money:
    amount: Decimal
    currency: str = "USD"

    def __post_init__(self):
        if self.amount < 0:
            raise ValueError("Amount cannot be negative")

    def add(self, other: 'Money') -> 'Money':
        if self.currency != other.currency:
            raise ValueError("Currency mismatch")
        return Money(self.amount + other.amount, self.currency)

    def multiply(self, multiplier: float) -> 'Money':
        return Money(self.amount * Decimal(str(multiplier)), self.currency)

    def __str__(self) -> str:
        return f"{self.currency} {self.amount:.2f}"

@dataclass(frozen=True)
class Address:
    street: str
    city: str
    state: str
    zip_code: str
    country: str

    def get_full_address(self) -> str:
        return f"{self.street}, {self.city}, {self.state} {self.zip_code}, {self.country}"

# ============== Vehicle Hierarchy ==============

class Vehicle(ABC):
    def __init__(self, license_plate: str, vehicle_type: VehicleType):
        self.license_plate = license_plate
        self.vehicle_type = vehicle_type

    def get_type(self) -> VehicleType:
        return self.vehicle_type

class Motorcycle(Vehicle):
    def __init__(self, license_plate: str):
        super().__init__(license_plate, VehicleType.MOTORCYCLE)

class Car(Vehicle):
    def __init__(self, license_plate: str):
        super().__init__(license_plate, VehicleType.CAR)

class Truck(Vehicle):
    def __init__(self, license_plate: str):
        super().__init__(license_plate, VehicleType.TRUCK)

class ElectricCar(Vehicle):
    def __init__(self, license_plate: str, battery_capacity: int):
        super().__init__(license_plate, VehicleType.ELECTRIC_CAR)
        self.battery_capacity = battery_capacity  # kWh
        self.current_charge = 50  # percentage

    def needs_charging(self) -> bool:
        return self.current_charge < 80

    def set_current_charge(self, charge: int):
        self.current_charge = max(0, min(100, charge))

# ============== Parking Spot Hierarchy ==============

class ParkingSpot(ABC):
    def __init__(self, spot_id: str, spot_type: SpotType, floor_number: int):
        self.spot_id = spot_id
        self.spot_type = spot_type
        self.floor_number = floor_number
        self.state = SpotState.AVAILABLE
        self.vehicle: Optional[Vehicle] = None
        self._lock = threading.Lock()

    @abstractmethod
    def can_fit_vehicle(self, vehicle: Vehicle) -> bool:
        pass

    def is_available(self) -> bool:
        with self._lock:
            return self.state == SpotState.AVAILABLE

    def assign_vehicle(self, vehicle: Vehicle):
        with self._lock:
            if not self.is_available():
                raise ValueError("Spot is not available")
            if not self.can_fit_vehicle(vehicle):
                raise ValueError("Vehicle cannot fit in this spot")
            self.vehicle = vehicle
            self.state = SpotState.OCCUPIED

    def release_vehicle(self):
        with self._lock:
            self.vehicle = None
            self.state = SpotState.AVAILABLE

    def reserve(self):
        with self._lock:
            if self.state != SpotState.AVAILABLE:
                raise ValueError("Cannot reserve non-available spot")
            self.state = SpotState.RESERVED

    def set_state(self, state: SpotState):
        with self._lock:
            self.state = state

class CompactSpot(ParkingSpot):
    def __init__(self, spot_id: str, floor_number: int):
        super().__init__(spot_id, SpotType.COMPACT, floor_number)

    def can_fit_vehicle(self, vehicle: Vehicle) -> bool:
        return vehicle.vehicle_type in [VehicleType.CAR, VehicleType.MOTORCYCLE]

class StandardSpot(ParkingSpot):
    def __init__(self, spot_id: str, floor_number: int):
        super().__init__(spot_id, SpotType.STANDARD, floor_number)

    def can_fit_vehicle(self, vehicle: Vehicle) -> bool:
        return vehicle.vehicle_type in [VehicleType.CAR, VehicleType.MOTORCYCLE, VehicleType.VAN]

class LargeSpot(ParkingSpot):
    def __init__(self, spot_id: str, floor_number: int):
        super().__init__(spot_id, SpotType.LARGE, floor_number)

    def can_fit_vehicle(self, vehicle: Vehicle) -> bool:
        return True  # Can fit any vehicle

class MotorcycleSpot(ParkingSpot):
    def __init__(self, spot_id: str, floor_number: int):
        super().__init__(spot_id, SpotType.MOTORCYCLE, floor_number)

    def can_fit_vehicle(self, vehicle: Vehicle) -> bool:
        return vehicle.vehicle_type == VehicleType.MOTORCYCLE

class ChargingStation:
    def __init__(self, station_id: str):
        self.station_id = station_id
        self.is_charging = False
        self.charging_start_time: Optional[datetime] = None
        self.kwh_charged = 0
        self.rate_per_kwh = Money(Decimal("0.30"))  # $0.30 per kWh

    def start_charging(self, car: ElectricCar):
        self.is_charging = True
        self.charging_start_time = datetime.now()

    def stop_charging(self):
        if self.is_charging and self.charging_start_time:
            duration = datetime.now() - self.charging_start_time
            hours = duration.total_seconds() / 3600
            self.kwh_charged = int(hours * 7)  # Assume 7 kW charger
            self.is_charging = False

    def calculate_charging_fee(self) -> Money:
        return self.rate_per_kwh.multiply(self.kwh_charged)

class ElectricSpot(ParkingSpot):
    def __init__(self, spot_id: str, floor_number: int):
        super().__init__(spot_id, SpotType.ELECTRIC, floor_number)
        self.charging_station = ChargingStation(f"{spot_id}-CHARGER")

    def can_fit_vehicle(self, vehicle: Vehicle) -> bool:
        return vehicle.vehicle_type == VehicleType.ELECTRIC_CAR

    def start_charging(self, car: ElectricCar):
        self.charging_station.start_charging(car)

    def stop_charging(self):
        self.charging_station.stop_charging()

    def get_charging_fee(self) -> Money:
        return self.charging_station.calculate_charging_fee()

# ============== Ticket ==============

class Ticket:
    def __init__(self, vehicle: Vehicle, spot: ParkingSpot):
        self.ticket_id = str(uuid.uuid4())
        self.issue_time = datetime.now()
        self.assigned_spot = spot
        self.vehicle = vehicle
        self.status = TicketStatus.ACTIVE
        self.paid_time: Optional[datetime] = None

    def get_parked_duration(self) -> timedelta:
        end_time = self.paid_time if self.paid_time else datetime.now()
        return end_time - self.issue_time

    def mark_paid(self):
        self.status = TicketStatus.PAID
        self.paid_time = datetime.now()

    def mark_lost(self):
        self.status = TicketStatus.LOST

# ============== Pricing Strategy Pattern ==============

class PricingStrategy(Protocol):
    def calculate_fee(self, ticket: Ticket) -> Money:
        ...

class HourlyPricing:
    def __init__(self):
        self.hourly_rates: Dict[VehicleType, Money] = {
            VehicleType.MOTORCYCLE: Money(Decimal("2.0")),
            VehicleType.CAR: Money(Decimal("5.0")),
            VehicleType.ELECTRIC_CAR: Money(Decimal("5.0")),
            VehicleType.TRUCK: Money(Decimal("10.0")),
            VehicleType.VAN: Money(Decimal("7.0")),
        }
        self.max_daily_rate = Money(Decimal("50.0"))

    def calculate_fee(self, ticket: Ticket) -> Money:
        duration = ticket.get_parked_duration()
        hours = duration.total_seconds() / 3600
        if hours < 1:
            hours = 1  # Minimum 1 hour
        else:
            hours = int(hours) + (1 if duration.total_seconds() % 3600 > 0 else 0)

        hourly_rate = self.hourly_rates[ticket.vehicle.vehicle_type]
        total_fee = hourly_rate.multiply(hours)

        # Apply daily cap
        if total_fee.amount > self.max_daily_rate.amount:
            return self.max_daily_rate

        return total_fee

    def update_rate(self, vehicle_type: VehicleType, new_rate: Money):
        self.hourly_rates[vehicle_type] = new_rate

class PeakHourPricing:
    def __init__(self, base_strategy: PricingStrategy, peak_multiplier: float):
        self.base_strategy = base_strategy
        self.peak_multiplier = peak_multiplier
        self.peak_hours = [
            (7, 10),   # 7 AM - 10 AM
            (16, 19),  # 4 PM - 7 PM
        ]

    def is_peak_hour(self, dt: datetime) -> bool:
        hour = dt.hour
        return any(start <= hour < end for start, end in self.peak_hours)

    def calculate_fee(self, ticket: Ticket) -> Money:
        base_fee = self.base_strategy.calculate_fee(ticket)

        if self.is_peak_hour(ticket.issue_time) or \
           (ticket.paid_time and self.is_peak_hour(ticket.paid_time)):
            return base_fee.multiply(self.peak_multiplier)

        return base_fee

@dataclass
class MonthlyPass:
    pass_id: str
    license_plate: str
    start_date: datetime
    end_date: datetime

    def is_valid(self) -> bool:
        now = datetime.now()
        return self.start_date <= now <= self.end_date

class MonthlyPassPricing:
    def __init__(self, fallback_strategy: PricingStrategy):
        self.active_passes: Dict[str, MonthlyPass] = {}
        self.fallback_strategy = fallback_strategy

    def calculate_fee(self, ticket: Ticket) -> Money:
        license_plate = ticket.vehicle.license_plate
        pass_obj = self.active_passes.get(license_plate)

        if pass_obj and pass_obj.is_valid():
            return Money(Decimal("0.0"))  # Free for monthly pass holders

        return self.fallback_strategy.calculate_fee(ticket)

    def add_pass(self, monthly_pass: MonthlyPass):
        self.active_passes[monthly_pass.license_plate] = monthly_pass

    def remove_pass(self, license_plate: str):
        self.active_passes.pop(license_plate, None)

# ============== Payment ==============

class Payment:
    def __init__(self, amount: Money, method: PaymentMethod):
        self.payment_id = str(uuid.uuid4())
        self.amount = amount
        self.method = method
        self.status = PaymentStatus.PENDING
        self.timestamp = datetime.now()
        self.transaction_ref: Optional[str] = None

    def process(self) -> bool:
        # Simulate payment processing
        time.sleep(0.1)  # Simulate network delay

        # 95% success rate for simulation
        import random
        if random.random() < 0.95:
            self.status = PaymentStatus.COMPLETED
            self.transaction_ref = f"TXN-{str(uuid.uuid4())[:8]}"
            return True
        else:
            self.status = PaymentStatus.FAILED
            return False

    def refund(self):
        if self.status == PaymentStatus.COMPLETED:
            self.status = PaymentStatus.REFUNDED

class Receipt:
    def __init__(self, ticket: Ticket, payment: Payment,
                 parking_fee: Money, charging_fee: Money):
        self.receipt_id = str(uuid.uuid4())
        self.ticket = ticket
        self.payment = payment
        self.parking_fee = parking_fee
        self.charging_fee = charging_fee
        self.total_fee = parking_fee.add(charging_fee)
        self.issue_time = datetime.now()

    def generate_receipt(self) -> str:
        lines = [
            "========== PARKING RECEIPT ==========",
            f"Receipt ID: {self.receipt_id}",
            f"Ticket ID: {self.ticket.ticket_id}",
            f"Vehicle: {self.ticket.vehicle.license_plate}",
            f"Entry Time: {self.ticket.issue_time}",
            f"Exit Time: {self.ticket.paid_time}",
            f"Duration: {self.ticket.get_parked_duration().total_seconds() / 3600:.1f} hours",
            f"Parking Fee: {self.parking_fee}",
        ]

        if self.charging_fee.amount > 0:
            lines.append(f"Charging Fee: {self.charging_fee}")

        lines.extend([
            f"Total: {self.total_fee}",
            f"Payment Method: {self.payment.method.value}",
            f"Transaction Ref: {self.payment.transaction_ref}",
            "====================================",
        ])

        return "\n".join(lines)

# ============== Display Board (Observer Pattern) ==============

class DisplayBoard:
    def __init__(self, floor_number: int):
        self.floor_number = floor_number
        self.available_counts: Dict[SpotType, int] = {
            spot_type: 0 for spot_type in SpotType
        }

    def update(self, spot_type: SpotType, available_count: int):
        self.available_counts[spot_type] = available_count

    def display_availability(self):
        icons = {
            SpotType.MOTORCYCLE: "๐Ÿ๏ธ",
            SpotType.COMPACT: "๐Ÿš—",
            SpotType.STANDARD: "๐Ÿš™",
            SpotType.LARGE: "๐Ÿš",
            SpotType.ELECTRIC: "โšก",
        }

        print(f"========== FLOOR {self.floor_number} ==========")
        for spot_type, count in self.available_counts.items():
            icon = icons.get(spot_type, "๐Ÿ…ฟ๏ธ")
            print(f"{icon} {spot_type.value.upper()}: {count} available")
        print("================================")

# ============== Parking Floor ==============

class ParkingFloor:
    def __init__(self, floor_number: int):
        self.floor_number = floor_number
        self.spots_by_type: Dict[SpotType, List[ParkingSpot]] = {
            spot_type: [] for spot_type in SpotType
        }
        self.display_board = DisplayBoard(floor_number)

    def add_spot(self, spot: ParkingSpot):
        self.spots_by_type[spot.spot_type].append(spot)
        self.update_display_board()

    def remove_spot(self, spot_id: str):
        for spots in self.spots_by_type.values():
            spots[:] = [s for s in spots if s.spot_id != spot_id]
        self.update_display_board()

    def find_available_spot(self, vehicle_type: VehicleType) -> Optional[ParkingSpot]:
        preferred_types = self._get_preferred_spot_types(vehicle_type)

        for spot_type in preferred_types:
            for spot in self.spots_by_type[spot_type]:
                if spot.is_available():
                    return spot

        return None

    def _get_preferred_spot_types(self, vehicle_type: VehicleType) -> List[SpotType]:
        preferences = {
            VehicleType.MOTORCYCLE: [SpotType.MOTORCYCLE, SpotType.COMPACT],
            VehicleType.CAR: [SpotType.COMPACT, SpotType.STANDARD],
            VehicleType.ELECTRIC_CAR: [SpotType.ELECTRIC, SpotType.STANDARD],
            VehicleType.VAN: [SpotType.STANDARD, SpotType.LARGE],
            VehicleType.TRUCK: [SpotType.LARGE],
        }
        return preferences.get(vehicle_type, [])

    def get_available_count(self, spot_type: SpotType) -> int:
        return sum(1 for spot in self.spots_by_type[spot_type] if spot.is_available())

    def update_display_board(self):
        for spot_type in SpotType:
            count = self.get_available_count(spot_type)
            self.display_board.update(spot_type, count)

# ============== Reservation ==============

class Reservation:
    def __init__(self, vehicle: Vehicle, start_time: datetime,
                 end_time: datetime, spot: ParkingSpot):
        self.reservation_id = str(uuid.uuid4())
        self.vehicle = vehicle
        self.start_time = start_time
        self.end_time = end_time
        self.reserved_spot = spot
        self.status = ReservationStatus.CONFIRMED

    def is_valid(self) -> bool:
        now = datetime.now()
        grace_period = timedelta(minutes=15)
        return (self.status == ReservationStatus.CONFIRMED and
                self.start_time <= now <= self.end_time + grace_period)

    def is_expired(self) -> bool:
        grace_period = timedelta(minutes=15)
        return datetime.now() > self.end_time + grace_period

    def cancel(self):
        self.status = ReservationStatus.CANCELLED
        if self.reserved_spot:
            self.reserved_spot.set_state(SpotState.AVAILABLE)

    def activate(self):
        self.status = ReservationStatus.ACTIVE

    def complete(self):
        self.status = ReservationStatus.COMPLETED

class ReservationManager:
    def __init__(self):
        self.reservations: Dict[str, Reservation] = {}
        self._start_expiry_monitor()

    def create_reservation(self, vehicle: Vehicle, start_time: datetime,
                          end_time: datetime, spot: ParkingSpot) -> Reservation:
        spot.reserve()
        reservation = Reservation(vehicle, start_time, end_time, spot)
        self.reservations[reservation.reservation_id] = reservation
        return reservation

    def validate_reservation(self, reservation_id: str) -> bool:
        reservation = self.reservations.get(reservation_id)
        return reservation is not None and reservation.is_valid()

    def get_reservation(self, reservation_id: str) -> Optional[Reservation]:
        return self.reservations.get(reservation_id)

    def cancel_reservation(self, reservation_id: str):
        reservation = self.reservations.get(reservation_id)
        if reservation:
            reservation.cancel()

    def _start_expiry_monitor(self):
        def monitor():
            while True:
                time.sleep(60)  # Check every minute
                for reservation in list(self.reservations.values()):
                    if (reservation.is_expired() and
                        reservation.status == ReservationStatus.CONFIRMED):
                        reservation.cancel()
                        print(f"Reservation {reservation.reservation_id} expired and cancelled")

        thread = threading.Thread(target=monitor, daemon=True)
        thread.start()

# ============== Entry and Exit Gates ==============

class EntranceGate:
    def __init__(self, gate_id: str, parking_lot: 'ParkingLot'):
        self.gate_id = gate_id
        self.parking_lot = parking_lot

    def issue_ticket(self, vehicle: Vehicle, reservation_id: Optional[str] = None) -> Ticket:
        # Check reservation first
        if reservation_id:
            if not self.parking_lot.reservation_manager.validate_reservation(reservation_id):
                raise ValueError("Invalid or expired reservation")

            reservation = self.parking_lot.reservation_manager.get_reservation(reservation_id)
            reservation.activate()

            ticket = Ticket(vehicle, reservation.reserved_spot)
            reservation.reserved_spot.assign_vehicle(vehicle)
            return ticket

        # Find available spot
        spot = self.parking_lot.get_available_spot(vehicle.vehicle_type)
        if not spot:
            raise ValueError(f"No available spots for vehicle type: {vehicle.vehicle_type}")

        spot.assign_vehicle(vehicle)
        ticket = Ticket(vehicle, spot)
        self.parking_lot.add_active_ticket(ticket)

        # Update display boards
        self.parking_lot.update_all_display_boards()

        print(f"โœ… Ticket issued: {ticket.ticket_id} | "
              f"Spot: {spot.spot_id} | Floor: {spot.floor_number}")

        return ticket

class ExitGate:
    def __init__(self, gate_id: str, parking_lot: 'ParkingLot'):
        self.gate_id = gate_id
        self.parking_lot = parking_lot

    def process_exit(self, ticket_id: str, payment_method: PaymentMethod) -> Receipt:
        ticket = self.parking_lot.get_active_ticket(ticket_id)
        if not ticket:
            raise ValueError("Invalid ticket ID")

        # Calculate parking fee
        parking_fee = self.parking_lot.pricing_strategy.calculate_fee(ticket)

        # Calculate charging fee if applicable
        charging_fee = Money(Decimal("0.0"))
        spot = ticket.assigned_spot
        if isinstance(spot, ElectricSpot):
            spot.stop_charging()
            charging_fee = spot.get_charging_fee()

        total_fee = parking_fee.add(charging_fee)

        # Process payment
        payment = Payment(total_fee, payment_method)
        if not payment.process():
            raise ValueError("Payment failed")

        # Mark ticket as paid and release spot
        ticket.mark_paid()
        spot.release_vehicle()
        self.parking_lot.remove_active_ticket(ticket_id)

        # Update display boards
        self.parking_lot.update_all_display_boards()

        # Generate receipt
        receipt = Receipt(ticket, payment, parking_fee, charging_fee)

        print("โœ… Exit processed successfully")
        print(receipt.generate_receipt())

        return receipt

# ============== Parking Lot (Singleton) ==============

class ParkingLot:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls, name: str = None, address: Address = None):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
                    cls._instance._initialized = False
        return cls._instance

    def __init__(self, name: str = None, address: Address = None):
        if self._initialized:
            return

        self.name = name
        self.address = address
        self.floors: List[ParkingFloor] = []
        self.entrance_gates: List[EntranceGate] = []
        self.exit_gates: List[ExitGate] = []
        self.pricing_strategy: PricingStrategy = HourlyPricing()
        self.reservation_manager = ReservationManager()
        self.active_tickets: Dict[str, Ticket] = {}
        self._initialized = True

    def add_floor(self, floor: ParkingFloor):
        self.floors.append(floor)

    def add_entrance_gate(self, gate: EntranceGate):
        self.entrance_gates.append(gate)

    def add_exit_gate(self, gate: ExitGate):
        self.exit_gates.append(gate)

    def get_available_spot(self, vehicle_type: VehicleType) -> Optional[ParkingSpot]:
        for floor in self.floors:
            spot = floor.find_available_spot(vehicle_type)
            if spot:
                return spot
        return None

    def get_total_capacity(self) -> int:
        total = 0
        for floor in self.floors:
            for spot_type in SpotType:
                total += floor.get_available_count(spot_type)
        return total

    def get_available_count(self, spot_type: SpotType) -> int:
        return sum(floor.get_available_count(spot_type) for floor in self.floors)

    def update_all_display_boards(self):
        for floor in self.floors:
            floor.update_display_board()

    def display_all_floors(self):
        for floor in self.floors:
            floor.display_board.display_availability()

    def add_active_ticket(self, ticket: Ticket):
        self.active_tickets[ticket.ticket_id] = ticket

    def remove_active_ticket(self, ticket_id: str):
        self.active_tickets.pop(ticket_id, None)

    def get_active_ticket(self, ticket_id: str) -> Optional[Ticket]:
        return self.active_tickets.get(ticket_id)

    def set_pricing_strategy(self, strategy: PricingStrategy):
        self.pricing_strategy = strategy

# ============== Admin and Attendant ==============

class Admin:
    def __init__(self, admin_id: str, name: str):
        self.admin_id = admin_id
        self.name = name

    def add_floor(self, parking_lot: ParkingLot, floor: ParkingFloor):
        parking_lot.add_floor(floor)
        print(f"Admin {self.name} added floor {floor.floor_number}")

    def add_spot(self, floor: ParkingFloor, spot: ParkingSpot):
        floor.add_spot(spot)
        print(f"Admin {self.name} added spot {spot.spot_id}")

    def update_pricing(self, parking_lot: ParkingLot, strategy: PricingStrategy):
        parking_lot.set_pricing_strategy(strategy)
        print(f"Admin {self.name} updated pricing strategy")

    def mark_spot_maintenance(self, spot: ParkingSpot):
        spot.set_state(SpotState.MAINTENANCE)
        print(f"Admin {self.name} marked spot {spot.spot_id} for maintenance")

class ParkingAttendant:
    def __init__(self, attendant_id: str, name: str):
        self.attendant_id = attendant_id
        self.name = name

    def assist_payment(self, exit_gate: ExitGate, ticket_id: str,
                      method: PaymentMethod) -> Receipt:
        print(f"Attendant {self.name} assisting with payment")
        return exit_gate.process_exit(ticket_id, method)

    def handle_lost_ticket(self, parking_lot: ParkingLot, vehicle: Vehicle) -> Ticket:
        print(f"Attendant {self.name} handling lost ticket for {vehicle.license_plate}")
        temp_spot = parking_lot.get_available_spot(vehicle.vehicle_type)
        lost_ticket = Ticket(vehicle, temp_spot)
        lost_ticket.mark_lost()
        return lost_ticket

# ============== Demo ==============

def main():
    print("๐Ÿ…ฟ๏ธ  Initializing Parking Lot System...\n")

    # Create parking lot
    address = Address("123 Main St", "San Francisco", "CA", "94101", "USA")
    parking_lot = ParkingLot("Downtown Parking", address)

    # Create floors
    floor1 = ParkingFloor(1)
    floor2 = ParkingFloor(2)

    # Add spots to floor 1
    for i in range(1, 6):
        floor1.add_spot(CompactSpot(f"F1-C{i}", 1))
    for i in range(1, 6):
        floor1.add_spot(StandardSpot(f"F1-S{i}", 1))
    for i in range(1, 4):
        floor1.add_spot(MotorcycleSpot(f"F1-M{i}", 1))
    for i in range(1, 3):
        floor1.add_spot(ElectricSpot(f"F1-E{i}", 1))

    # Add spots to floor 2
    for i in range(1, 6):
        floor2.add_spot(StandardSpot(f"F2-S{i}", 2))
    for i in range(1, 4):
        floor2.add_spot(LargeSpot(f"F2-L{i}", 2))

    # Add floors to parking lot
    parking_lot.add_floor(floor1)
    parking_lot.add_floor(floor2)

    # Create gates
    entrance1 = EntranceGate("ENT-1", parking_lot)
    exit1 = ExitGate("EXIT-1", parking_lot)
    parking_lot.add_entrance_gate(entrance1)
    parking_lot.add_exit_gate(exit1)

    # Display initial availability
    print("๐Ÿ“Š Initial Parking Availability:")
    parking_lot.display_all_floors()
    print()

    # Scenario 1: Regular car entry and exit
    print("๐Ÿš— Scenario 1: Car Entry and Exit\n")
    car1 = Car("ABC-123")
    ticket1 = entrance1.issue_ticket(car1)

    time.sleep(2)  # Simulate parking time

    receipt1 = exit1.process_exit(ticket1.ticket_id, PaymentMethod.CREDIT_CARD)
    print()

    # Scenario 2: Electric car with charging
    print("โšก Scenario 2: Electric Car with Charging\n")
    electric_car = ElectricCar("TESLA-X", 75)
    ticket2 = entrance1.issue_ticket(electric_car)

    if isinstance(ticket2.assigned_spot, ElectricSpot):
        ticket2.assigned_spot.start_charging(electric_car)
        print(f"โšก Charging started at {ticket2.assigned_spot.spot_id}")

    time.sleep(3)  # Simulate charging time

    receipt2 = exit1.process_exit(ticket2.ticket_id, PaymentMethod.MOBILE_PAYMENT)
    print()

    # Scenario 3: Reservation
    print("๐Ÿ“… Scenario 3: Advance Reservation\n")
    car2 = Car("XYZ-789")
    reservation_start = datetime.now()
    reservation_end = reservation_start + timedelta(hours=2)

    spot_for_reservation = parking_lot.get_available_spot(VehicleType.CAR)
    reservation = parking_lot.reservation_manager.create_reservation(
        car2, reservation_start, reservation_end, spot_for_reservation
    )

    print(f"โœ… Reservation created: {reservation.reservation_id}")
    print(f"   Spot: {spot_for_reservation.spot_id}")
    print(f"   Valid until: {reservation_end + timedelta(minutes=15)}")
    print()

    # Use reservation
    ticket3 = entrance1.issue_ticket(car2, reservation.reservation_id)
    print("โœ… Reservation activated, vehicle parked\n")

    # Display final availability
    print("๐Ÿ“Š Final Parking Availability:")
    parking_lot.display_all_floors()

    print("\nโœ… Parking Lot System Demo Complete!")

if __name__ == "__main__":
    main()

Key Design Decisions

  1. Strategy Pattern for Pricing: Allows flexible pricing models (hourly, peak-hour, monthly passes) without modifying core logic. New pricing strategies can be added by implementing the PricingStrategy interface.

  2. Observer Pattern for Display Boards: Display boards automatically update when spot availability changes, ensuring real-time accuracy across all floors.

  3. State Pattern for Parking Spots: Spots transition through states (Available โ†’ Reserved โ†’ Occupied โ†’ Available) with proper state validation to prevent invalid operations.

  4. Singleton Pattern for ParkingLot: Ensures centralized management of all parking operations with thread-safe instance creation.

  5. Factory Pattern Consideration: While not explicitly implemented, the spot creation could be enhanced with a ParkingSpotFactory for more complex spot configurations.

  6. Thread Safety: Critical sections use locks/synchronized methods to handle concurrent entry/exit operations safely.

  7. Money Value Object: Immutable Money class prevents currency mismatch errors and precision loss in financial calculations.

  8. Reservation Expiry Monitoring: Background thread automatically cancels expired reservations, maintaining system consistency without manual intervention.

Comments