package org.switchyard.quickstarts.demos.library;

import java.io.Serializable;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

import org.switchyard.quickstarts.demos.library.types.Book;
import org.switchyard.quickstarts.demos.library.types.Loan;
import org.switchyard.quickstarts.demos.library.types.ObjectFactory;

@SuppressWarnings("serial")
public final class Library implements Serializable {

    private static final Library INSTANCE = new Library();
    private static final Integer ZERO = Integer.valueOf(0);

    private final Map<String, Book> isbns_to_books = new TreeMap<String, Book>();
    private final Map<String, Integer> isbns_to_quantities = Collections.synchronizedMap(new TreeMap<String, Integer>());
    private final Object librarian = new Object();

    private Library() {
        addBook("978-0-307-35193-7", "World War Z", "An Oral History of the Zombie War", 1);
        addBook("978-0-7360-9829-8", "Successful Sports Officiating", "American Sports Education Program.", 3);
        addBook("978-0-7434-8773-3", "The Time Machine", "H. G. Wells' story of a time traveler.", 8);
        addBook("978-1-101-15402-1", "The Island of Dr. Moreau", "H. G. Wells' story of what may be most relevant to modern ethical dimemmas.", 5);
        addBook("978-1-4000-5-80-2", "The Zombie Survival Guide", "Complete Protection from the Living Dead", 6);
        addBook("978-1-448-14153-1", "Doctor Who: Summer Falls", "Story of Amelia Williams.", 2);
        addBook("978-1-4516-7486-6", "Tesla, Man Out of Time", "Explores the brilliant and prescient mind of one of the twentieth century's greatest scientists and inventors, Nikola Tesla.", 4);
        addBook("978-1-59474-449-5", "Pride and Prejudice and Zombies", "The Classic Regency Romance -- Now with Ultraviolent Zombie Mayhem!", 7);
    }

    private void addBook(String isbn, String title, String synopsis, int quantity) {
        Book book = new Book();
        book.setIsbn(isbn);
        book.setTitle(title);
        book.setSynopsis(synopsis);
        isbns_to_books.put(isbn, book);
        isbns_to_quantities.put(isbn, quantity);
    }

    public Collection<Book> getAllBooks() {
        return isbns_to_books.values();
    }

    public Collection<Book> getAvailableBooks() {
        synchronized (librarian) {
            Collection<Book> books = new LinkedList<Book>();
            for (Entry<String, Integer> entry : isbns_to_quantities.entrySet()) {
                if (entry.getValue() > 0) {
                    books.add(getBook(entry.getKey()));
                }
            }
            return books;
        }
    }

    public Book getBook(String isbn) {
        return isbns_to_books.get(isbn);
    }

    public Integer getQuantity(String isbn) {
        Integer quantity = null;
        if (isbn != null) {
            synchronized (librarian) {
                quantity = isbns_to_quantities.get(isbn);
            }
        }
        return quantity != null ? quantity : ZERO;
    }

    public Integer getQuantity(Book book) {
        return book != null ? getQuantity(book.getIsbn()) : ZERO;
    }

    public Loan attemptLoan(String isbn, String loanId) {
        Loan loan = new Loan();
        loan.setId(loanId);
        Book book = getBook(isbn);
        if (book != null) {
            synchronized (librarian) {
                int quantity = getQuantity(book);
                if (quantity > 0) {
                    quantity--;
                    isbns_to_quantities.put(isbn, quantity);
                    loan.setApproved(true);
                    loan.setNotes("Happy reading! Remaining copies: " + quantity);
                    loan.setBook(book);
                } else {
                    loan.setApproved(false);
                    loan.setNotes("Book has no copies available.");
                }
            }
        } else {
            loan.setApproved(false);
            loan.setNotes("No book matching isbn: " + isbn);
        }
        return loan;
    }

    public boolean returnLoan(Loan loan) {
        if (loan != null) {
            Book book = loan.getBook();
            if (book != null) {
                String isbn = book.getIsbn();
                if (isbn != null) {
                    synchronized (librarian) {
                        Integer quantity = isbns_to_quantities.get(isbn);
                        if (quantity != null) {
                            quantity = new Integer(quantity.intValue() + 1);
                            isbns_to_quantities.put(isbn, quantity);
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    public String toString() {
        return toString(false);
    }

    public String toString(boolean detailed) {
        StringWriter sw = new StringWriter();
        synchronized (librarian) {
            try {
                if (detailed) {
                    JAXBContext ctx = JAXBContext.newInstance("org.switchyard.quickstarts.demos.library.types");
                    Marshaller m = ctx.createMarshaller();
                    m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
                    m.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
                    ObjectFactory of = new ObjectFactory();
                    for (Book book : isbns_to_books.values()) {
                        int quantity = isbns_to_quantities.get(book.getIsbn());
                        sw.write("\nBook (quantity=" + quantity + ")\n");
                        m.marshal(of.createBook(book), sw);
                        sw.write('\n');
                    }
                } else {
                    for (Book book : isbns_to_books.values()) {
                        int quantity = isbns_to_quantities.get(book.getIsbn());
                        sw.write(book.getTitle() + " (" + quantity + ")\n");
                    }
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
        return sw.toString().trim();
    }

    public static final Library library() {
        return INSTANCE;
    }

    public static void main(String... args) {
        System.out.println(library().toString(false));
        System.out.println();
        System.out.println(library().toString(true));
    }

}
