package org.wikiwebserver.util.connector;

import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;

import org.wikiwebserver.core.WareHouse;
import org.wikiwebserver.core.WikiMap;
import org.wikiwebserver.handler.http.*;
import org.wikiwebserver.handler.http.interfaces.HTTPResponder;

public class WikiMapConnector implements HTTPResponder {
	
    public Object respond(HTTPHandler conn) throws IOException {
        
        String url = conn.getRequest().getUrl();
        String prefix = "/WikiMap/";
        if (!url.startsWith(prefix)) {
            throw new HTTPException(400, "Incorrect WikiMap path syntax");
        }
        
        
        String path = url.substring(prefix.length());        
        
        String method = conn.getRequest().getMethod();
        if ("GET".equalsIgnoreCase(method)) {
            
            Object obj = getMapObject(path);
            if (obj == null) {
                throw new HTTPException(404, "Failed to find WikiMap object: " + path);
            }     
            
            // Package inner WikiMaps as a HashMap
            if (obj instanceof WikiMap) {
                obj = new HashMap<String, Object>((WikiMap)obj);
            }
            
            return objectToBytes(obj, conn);
            
        }
        else {
            
            String lenString = null;
            if ("PUT".equalsIgnoreCase(method) || "POST".equalsIgnoreCase(method)) {
                lenString = conn.getRequest().getHeaders().getFirst("Content-Length");
                if (lenString == null) {
                    throw new HTTPException(400, "Length of posted object must be specified");
                }
            }
            
            try {
                
                if (true) throw new SecurityException("Modification of store not permitted");
                
               
                if ("PUT".equalsIgnoreCase(method) || "POST".equalsIgnoreCase(method)) {
                    // Read data sent with the request
                    int length = Integer.parseInt(lenString);
                    byte[] bytes = conn.getInputStream().readBytes(length);
                    Object obj = bytesToObject(bytes, conn);
                    if ("PUT".equalsIgnoreCase(method)) {
                        putMapObject(path, obj);
                    }
                    else if ("POST".equalsIgnoreCase(method)) {
                        postStoreObject(path, obj);
                    }
                }
                else if ("DELETE".equalsIgnoreCase(method)) {
                    deleteStoreObject(path);  
                }
                
                
            } catch (Exception ex) {
                throw new HTTPException(500, ex.getMessage(), ex);
            }
        }
        
        return null;
    }

    
    private byte[] objectToBytes(Object obj, HTTPHandler conn) throws IOException {
        
        ByteArrayOutputStream bos = new ByteArrayOutputStream();       
        
        String accept = conn.getRequest().getHeaders().getFirst("Accept");   
        if (accept != null && accept.contains("application/x-java-serialized-object")) {
            conn.getResponse().getHeaders().set("Content-Type", "application/x-java-serialized-object"); 
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(obj);
            oos.close();
        }
        else {
            conn.getResponse().getHeaders().set("Content-Type", "text/xml");        
            XMLEncoder xenc = new XMLEncoder(bos);        
            xenc.writeObject(obj);
            xenc.close();
        }
        
        return bos.toByteArray();
    }    
    
    private Object bytesToObject(byte[] raw, HTTPHandler conn) throws Exception {
        
        ByteArrayInputStream bis = new ByteArrayInputStream(raw);       
        
        String type = conn.getRequest().getHeaders().getFirst("Content-Type");  
        if (type != null && type.equals("application/x-java-serialized-object")) {
            ObjectInputStream oos = new ObjectInputStream(bis);
            return oos.readObject();
        }
        else if (type != null && type.equals("text/xml")) {    
            XMLDecoder xenc = new XMLDecoder(bis);        
            return xenc.readObject();
        }
        
        return null;
    }    
    
    
    private Object getMapObject(String path) {
        
        WikiMap parentStore = WareHouse.getWikiMap();
        if (path.length() == 0) return parentStore;
        
        for (String ele : path.split("/")) {
            Object obj = (Object) parentStore.get(ele);
            if (obj instanceof WikiMap) {
                parentStore = (WikiMap) obj;
            }
            else return obj;
        }
        return parentStore;        
    }
    
    private Object putMapObject(String path, Object newObj) throws IOException {
        
        String[] hierarchy = path.split("/");
        String[] storeRef = new String[hierarchy.length-1];
        String key = hierarchy[hierarchy.length-1];
        System.arraycopy(hierarchy, 0, storeRef, 0, storeRef.length);
        
        WikiMap store = WareHouse.getWikiMap(storeRef);
        if (store.get(key) != null) {
            throw new IOException("WikiStore object already exists");
        }
        
        return store.put(key, newObj);
    }   
    
    private Object postStoreObject(String path, Object newObj) throws IOException {
        
        String[] hierarchy = path.split("/");
        String[] storeRef = new String[hierarchy.length-1];
        String key = hierarchy[hierarchy.length-1];
        System.arraycopy(hierarchy, 0, storeRef, 0, storeRef.length);
        
        WikiMap store = WareHouse.getWikiMap(storeRef);
        if (store.get(key) == null) {
            throw new IOException("WikiMap object does not exist");
        }
        
        return store.put(key, newObj);
    }  
    
    private Object deleteStoreObject(String path) throws IOException {
        return postStoreObject(path, null);
    }     
}
