import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.*;

/**
 * Stage 1: a whole library management system in one file, no Maven, no framework.
 * Run it with:  java LibraryConsole.java < demo-input.txt     (JDK 25; JDK 11+ can launch single files)
 *
 * Commands (one per line):
 *   add-book isbn|title|author|copies    add-member name
 *   borrow isbn memberId [yyyy-mm-dd]    return loanId [yyyy-mm-dd]
 *   report                               quit
 */
public class LibraryConsole {

    static final int LOAN_DAYS = 14, MAX_LOANS = 3, FINE_PER_DAY = 2;

    static class Book {
        String isbn, title, author;
        int available;
        Book(String isbn, String title, String author, int copies) {
            this.isbn = isbn; this.title = title; this.author = author; this.available = copies;
        }
    }

    record Member(int id, String name) { }

    static class Loan {
        int id; Book book; Member member; LocalDate due, returned;
    }

    static Map<String, Book> books = new LinkedHashMap<>();
    static List<Member> members = new ArrayList<>();
    static List<Loan> loans = new ArrayList<>();

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        while (in.hasNextLine()) {
            String line = in.nextLine().trim();
            if (line.isEmpty()) continue;
            System.out.println("> " + line);
            String[] cmd = line.split(" ", 2);
            String rest = cmd.length > 1 ? cmd[1] : "";
            try {
                switch (cmd[0]) {
                    case "add-book" -> { String[] p = rest.split("\\|"); addBook(p[0], p[1], p[2], Integer.parseInt(p[3])); }
                    case "add-member" -> { members.add(new Member(members.size() + 1, rest)); System.out.println("Added member #" + members.size() + ": " + rest); }
                    case "borrow" -> { String[] p = rest.split(" "); borrow(p[0], Integer.parseInt(p[1]), p.length > 2 ? LocalDate.parse(p[2]) : LocalDate.now()); }
                    case "return" -> { String[] p = rest.split(" "); giveBack(Integer.parseInt(p[0]), p.length > 1 ? LocalDate.parse(p[1]) : LocalDate.now()); }
                    case "report" -> report();
                    case "quit" -> { return; }
                    default -> System.out.println("ERROR: unknown command '" + cmd[0] + "'");
                }
            } catch (RuntimeException e) {
                System.out.println("ERROR: bad input (" + e.getClass().getSimpleName() + ")");
            }
        }
    }

    static void addBook(String isbn, String title, String author, int copies) {
        if (books.containsKey(isbn)) { System.out.println("ERROR: ISBN " + isbn + " already exists"); return; }
        books.put(isbn, new Book(isbn, title, author, copies));
        System.out.println("Added book: " + title + " (" + copies + (copies == 1 ? " copy)" : " copies)"));
    }

    static void borrow(String isbn, int memberId, LocalDate on) {
        Book book = books.get(isbn);
        if (book == null) { System.out.println("ERROR: no such book " + isbn); return; }
        if (memberId < 1 || memberId > members.size()) { System.out.println("ERROR: no such member #" + memberId); return; }
        Member member = members.get(memberId - 1);
        if (book.available == 0) { System.out.println("ERROR: no copies of '" + book.title + "' are available"); return; }
        long active = loans.stream().filter(l -> l.returned == null && l.member.equals(member)).count();
        if (active >= MAX_LOANS) { System.out.println("ERROR: " + member.name() + " already has " + active + " active loans (max " + MAX_LOANS + ")"); return; }
        Loan loan = new Loan();
        loan.id = loans.size() + 1; loan.book = book; loan.member = member; loan.due = on.plusDays(LOAN_DAYS);
        loans.add(loan);
        book.available--;
        System.out.println("Loan #" + loan.id + ": " + member.name() + " borrowed '" + book.title + "', due " + loan.due);
    }

    static void giveBack(int loanId, LocalDate on) {
        if (loanId < 1 || loanId > loans.size()) { System.out.println("ERROR: no such loan #" + loanId); return; }
        Loan loan = loans.get(loanId - 1);
        if (loan.returned != null) { System.out.println("ERROR: loan #" + loanId + " was already returned on " + loan.returned); return; }
        loan.returned = on;
        loan.book.available++;
        long late = ChronoUnit.DAYS.between(loan.due, on);
        int fine = late > 0 ? (int) late * FINE_PER_DAY : 0;
        System.out.println("Loan #" + loanId + " returned on " + on + (fine > 0 ? ", fine Rs " + fine : ", no fine"));
    }

    static void report() {
        books.values().forEach(b -> System.out.println("  " + b.title + " by " + b.author + ": " + b.available + " available"));
        loans.stream().filter(l -> l.returned == null).forEach(l ->
                System.out.println("  ON LOAN #" + l.id + ": '" + l.book.title + "' -> " + l.member.name() + ", due " + l.due));
    }
}
