package org.zkoss.zssex.app.impl;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.zkoss.lang.Library;
import org.zkoss.util.logging.Log;
import org.zkoss.zk.ui.event.EventQueues;
import org.zkoss.zss.api.model.Book;
import org.zkoss.zss.app.BookInfo;
import org.zkoss.zss.app.BookRepository;
import org.zkoss.zss.app.impl.BookManagerImpl;


public class BookManagerEx extends BookManagerImpl {
	private static final long serialVersionUID = 3780790531468512803L;

	public BookManagerEx(BookRepository repo) {
		super(repo);
		String orgPeriod = Library.getProperty("zssapp.save.period.second");
		int period = orgPeriod == null ? DEFAULT_SAVE_PERIOD : Integer.parseInt(orgPeriod);
		
		if (period > 0) {
			scheduler = Executors.newSingleThreadScheduledExecutor();
			scheduler.scheduleAtFixedRate(new Runnable() {
				
				@Override
				public void run() {
					try {
						BookManagerEx.this.saveAll();
					} catch (IOException e) {
						throw new RuntimeException(e);
					}
				}
			}, 0, period, TimeUnit.SECONDS);
		}
	}

	private static final Log logger = Log.lookup(BookManagerEx.class.getName());
	
	private Map<String, BookInfo> infos = new HashMap<String, BookInfo>(repo.list().size() + 5);
	private Map<String, Book> books = new HashMap<String, Book>(repo.list().size() + 5);
	
	private final int DEFAULT_SAVE_PERIOD = 15 * 60; // 15 minutes, unit is second
	private ScheduledExecutorService scheduler;
	
	@Override
	public Book readBook(BookInfo info) throws IOException {
		synchronized (books) {
			String bookName = info.getName();
			if(books.containsKey(bookName))
				return books.get(bookName);
			
			Book book = repo.load(info);
			book.setShareScope(EventQueues.APPLICATION);
			books.put(bookName, book);
			infos.put(bookName, info);
			
			return book;
		}
	}

	@Override
	public BookInfo updateBook(BookInfo info, Book book) throws IOException {
		synchronized (books) {
			return repo.save(info, books.get(info.getName()));
		}
	}

	@Override
	public BookInfo saveBook(BookInfo info, Book book) throws IOException {
		synchronized (books) {
			String name = info.getName();
			if(books.containsKey(name)) {
				repo.save(info, books.get(info.getName()), true);
				return info;
			}
			
			BookInfo newInfo = repo.saveAs(info.getName(), book);
			return newInfo;
		}
	}

	@Override
	public void deleteBook(BookInfo info) throws IOException {
		synchronized (books) {
			String name = info.getName();
			books.remove(name);
			infos.remove(name);
			repo.delete(info);
		}
	}

	@Override
	public void detachBook(BookInfo info) throws IOException {
		synchronized (books) {
			String name = info.getName();
			updateBook(info, books.get(name));
			infos.remove(name);
			books.remove(name);
		}
	}
	
	@Override
	public boolean isBookAttached(BookInfo info) {
		synchronized (books) {
			return books.containsKey(info.getName());
		}
	}

	@Override
	public void saveAll() throws IOException {
		Set<String> keys = null;
		synchronized (books) {
			keys = new HashSet<String>(books.keySet());
		}
		
		for (String name : keys) {
			Book book = books.get(name);
			if(book != null)
				updateBook(infos.get(name), book);
		}
	}

	@Override
	public void shutdownAutoFileSaving() {
		scheduler.shutdownNow();
	}
	
	
}