package jp.ac.osaka_u.sanken.sparql.edit;

import java.util.ArrayList;
import java.util.List;

import org.openrdf.repository.RepositoryConnection;

import com.franz.agraph.jena.AGGraph;
import com.franz.agraph.jena.AGGraphMaker;
import com.franz.agraph.jena.AGModel;
import com.franz.agraph.repository.AGCatalog;
import com.franz.agraph.repository.AGRepository;
import com.franz.agraph.repository.AGRepositoryConnection;
import com.franz.agraph.repository.AGServer;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.impl.PropertyImpl;

import jp.ac.osaka_u.sanken.sparql.AllegroAccessor;

/**
 * SparqlEndpointとして公開されているAllegroGraphのRepositoryに対して編集を行うためのクラス
 * @author kato
 *
 */
public class AllegroEditor {
	
	private AllegroAccessor accessor;
	
	private String user;
	
	private String pass;
	
	private String url;
	
	private String repository;
	
	
	public AllegroEditor(AllegroAccessor sa, String user, String pass) throws Exception{
		this.accessor = sa;
		
		this.user = user;
		this.pass = pass;
		
		
		init();
	}

	public AllegroEditor(AllegroAccessor sa, String url, String repo, String user, String pass) throws Exception{
		this.accessor = sa;

		this.url = url;
		this.repository = repo;

		this.user = user;
		this.pass = pass;

		try {
			getModel();
		} catch(Exception e){
			throw new Exception("AllegroGraphのrepository設定が不正です", e);
		}
	}

	
	private void init() throws Exception {
		String endpoint = this.accessor.getSetting().getEndpoint();
		
		int index = endpoint.indexOf("/endpoint/");
		if (index >= 0){
			this.url = endpoint.substring(0, index);
			this.repository = endpoint.substring(index + "/endpoint/".length());
			System.out.println("url:"+url);
			System.out.println("repo:"+repository);
		} else {
			throw new Exception("AllegroGraphサーバではありません");
		}
	}
	
	public AllegroAccessor getAccessor(){
		return this.accessor;
	}
	
	/**
	 * 破棄時にcloseする
	 */
	public void close(){
		closeAll();
	}
	
	public boolean contains(RDFNode s, RDFNode p, RDFNode o) throws Exception{
		Model model = getModel();
		return contains(s, p, o, model);
	}

	
	private boolean isTriple(RDFNode s, RDFNode p, RDFNode o){
		if (!s.isResource()){
			return false;
		}
		if (!p.isResource()){
			return false;
		}
		return true;
	}

	private boolean contains(RDFNode s, RDFNode p, RDFNode o, Model model){
		if (!isTriple(s, p, o)){
			return false;
		}
		Resource ss = s.asResource();
		Property pp = new PropertyImpl(p.asResource().getURI());
		
		return model.contains(ss, pp, o);
	}

	
	
	public boolean add(RDFNode s, RDFNode p, String o) throws Exception{
		Model model = getModel();
		RDFNode oo = model.createLiteral(o);
		return add(s, p, oo, model);
	}

	public boolean add(RDFNode s, RDFNode p, RDFNode o) throws Exception{
		Model model = getModel();
		return add(s, p, o, model);
	}

	private boolean add(RDFNode s, RDFNode p, RDFNode o, Model model){
		if (!isTriple(s, p, o)){
			return false;
		}

		Resource ss = s.asResource();
		Property pp = new PropertyImpl(p.asResource().getURI());

		if (!model.contains(ss, pp, o)){
			model.add(ss, pp, o);
			return true;
		}
		return false;
	}

	public boolean delete(RDFNode s, RDFNode p, RDFNode o) throws Exception{
		Model model = getModel();
		return delete(s, p, o, model);
	}

	private boolean delete(RDFNode s, RDFNode p, RDFNode o, Model model){
		if (!isTriple(s, p, o)){
			return false;
		}

		Resource ss = s.asResource();
		Property pp = new PropertyImpl(p.asResource().getURI());

		if (model.contains(ss, pp, o)){
			model.remove(ss, pp, o);
			return true;
		}
		return false;
	}

	
	public boolean edit(RDFNode s, RDFNode p, RDFNode o, RDFNode s2, RDFNode p2, RDFNode o2) throws Exception{
		Model model = getModel();
		return edit(s, p, o, s2, p2, o2, model);
	}

	private boolean edit(RDFNode s, RDFNode p, RDFNode o, RDFNode s2, RDFNode p2, RDFNode o2, Model model){
		if (!isTriple(s, p, o) || !isTriple(s2, p2, o2)){
			return false;
		}

		Resource ss = s.asResource();
		Property pp = new PropertyImpl(p.asResource().getURI());
		Resource ss2 = s2.asResource();
		Property pp2 = new PropertyImpl(p2.asResource().getURI());
		if (model.contains(ss, pp, o)){
			model.remove(ss, pp, o);
			model.add(ss2, pp2, o2);
			return true;
		}
		return false;
	}
	
	
	
	public Model getModel() throws Exception {
		AGServer server = new AGServer(this.url, this.user, this.pass);
		AGCatalog catalog = server.getCatalog("/");
		AGRepository myRepository = catalog.openRepository(this.repository);

		AGRepositoryConnection conn = myRepository.getConnection();
		closeBeforeExit(conn);
		AGGraphMaker maker = new AGGraphMaker(conn);
        
		AGGraph graph = maker.getGraph();
		AGModel model = new AGModel(graph);
		
		closeBeforeExit(myRepository, model);

		return model;

	}
	
	private void closeBeforeExit(AGRepository rep, AGModel model) {
		toCloseRep.add(rep);
		toCloseModel.add(model);
	}
	
	
	protected void closeAll() {
		while (toCloseRep.isEmpty() == false) {
			AGRepository conn = toCloseRep.get(0);
			close(conn);
			while (toCloseRep.remove(conn)) {
			}
		}

		while (toCloseModel.isEmpty() == false) {
			AGModel conn = toCloseModel.get(0);
			close(conn);
			while (toCloseModel.remove(conn)) {
			}
		}

		while (toClose.isEmpty() == false) {
			RepositoryConnection conn = toClose.get(0);
			close(conn);
			while (toClose.remove(conn)) {
			}
		}

	}

	
    static void close(RepositoryConnection conn) {
        try {
            conn.close();
        } catch (Exception e) {
            System.err.println("Error closing repository connection: " + e);
            e.printStackTrace();
        }
    }
    
	void close(AGRepository conn) {
		try {
			conn.shutDown();
		} catch (Exception e) {
			System.err.println("Error closing repository repository: " + e);
			e.printStackTrace();
		}
	}

	void close(AGModel conn) {
		try {
			conn.close();
		} catch (Exception e) {
			System.err.println("Error closing repository model: " + e);
			e.printStackTrace();
		}
	}

	
    private static List<RepositoryConnection> toClose = new ArrayList<RepositoryConnection>();
	private static List<AGRepository> toCloseRep = new ArrayList<AGRepository>();
	private static List<AGModel> toCloseModel = new ArrayList<AGModel>();

    private static void closeBeforeExit(RepositoryConnection conn) {
        toClose.add(conn);
    }
    
}
