/*
 * Singleton — one shared instance of a class.
 *
 * Use it for a logger, config, or one in-memory store.
 * Avoid it when tests need to mock the object.
 *
 * These are different ways to build a singleton. Not all of them are safe.
 *
 *   1. Lazy
 *      Create on first getInstance().
 *      Broken under threads: two callers can both see null and both call new.
 *
 *   2. synchronized getInstance()
 *      Same lazy idea, lock on the whole method.
 *      Correct, but every later call still pays for the lock.
 *
 *   3. Eager
 *      Instance created at field declaration. getInstance only returns it.
 *      Private constructor is still required, otherwise anyone can call new.
 *      Cannot pass runtime constructor args. If the constructor throws,
 *      the app never starts.
 *
 *   4. Double-check locking
 *      Check null, lock, check null again.
 *      Correct and faster after the object exists.
 *      volatile is visibility across threads, not locking.
 *
 *   5. Enum   ← use this in Java
 *      Best approach here. The language gives you one instance.
 *      Serialization cannot manufacture a second object, which is how
 *      a normal class singleton can break.
 *
 * BookingStore below uses double-check locking as a worked example of
 * a shared in-memory map (one store, two services, same bookings).
 * If this were production Java, BookingStore would be an enum instead.
 */

enum EagerDatabase {
    INSTANCE;

    public void query(String sql) {
        System.out.println("[eager-enum] " + sql);
    }
}

class LazyUnsafeDatabase {
    private static LazyUnsafeDatabase instance;

    private LazyUnsafeDatabase() {}

    public static LazyUnsafeDatabase getInstance() {
        if (instance == null) {
            instance = new LazyUnsafeDatabase();
        }
        return instance;
    }
}

class SynchronizedDatabase {
    private static SynchronizedDatabase instance;

    private SynchronizedDatabase() {}

    public static synchronized SynchronizedDatabase getInstance() {
        if (instance == null) {
            instance = new SynchronizedDatabase();
        }
        return instance;
    }
}

class EagerDatabaseClass {
    private static final EagerDatabaseClass INSTANCE = new EagerDatabaseClass();

    private EagerDatabaseClass() {}

    public static EagerDatabaseClass getInstance() {
        return INSTANCE;
    }
}

class DoubleCheckDatabase {
    private static DoubleCheckDatabase instance;

    private DoubleCheckDatabase() {}

    public static DoubleCheckDatabase getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckDatabase.class) {
                if (instance == null) {
                    instance = new DoubleCheckDatabase();
                }
            }
        }
        return instance;
    }
}

/*
 * A place Singleton actually fits: one in-memory booking map that every
 * service must see. Two instances would mean two different "databases".
 */
class BookingStore {
    private static BookingStore instance;
    private final java.util.Map<String, String> bookings;

    private BookingStore() {
        bookings = new java.util.HashMap<String, String>();
    }

    public static BookingStore getInstance() {
        if (instance == null) {
            synchronized (BookingStore.class) {
                if (instance == null) {
                    instance = new BookingStore();
                }
            }
        }
        return instance;
    }

    public void book(String seat, String user) {
        bookings.put(seat, user);
    }

    public String holderOf(String seat) {
        return bookings.get(seat);
    }

    public int size() {
        return bookings.size();
    }
}

public class Main {
    public static void main(String[] args) {
        EagerDatabaseClass a = EagerDatabaseClass.getInstance();
        EagerDatabaseClass b = EagerDatabaseClass.getInstance();
        System.out.println("eager same object? " + (a == b));

        DoubleCheckDatabase c = DoubleCheckDatabase.getInstance();
        DoubleCheckDatabase d = DoubleCheckDatabase.getInstance();
        System.out.println("double-check same object? " + (c == d));

        EagerDatabase.INSTANCE.query("select 1");
        System.out.println("enum same object? " + (EagerDatabase.INSTANCE == EagerDatabase.INSTANCE));

        BookingStore payments = BookingStore.getInstance();
        BookingStore catalogue = BookingStore.getInstance();
        payments.book("A12", "Harsha");
        System.out.println("catalogue sees the same booking? " + catalogue.holderOf("A12"));
        System.out.println("store size from either reference: " + catalogue.size());
        System.out.println("payments == catalogue? " + (payments == catalogue));
    }
}