package net.methodyne.bellvue.store;

import java.io.IOException;
import java.util.Collection;
import java.util.Vector;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

import com.jamonapi.*;
import net.methodyne.bellvue.system.User;
import net.methodyne.bellvue.system.Group;
import net.methodyne.bellvue.core.Sdata;
import net.methodyne.bellvue.core.Finder;

/**
 * The datastore functions for inserting, update, delete and locking are accessible by this.
 *
 *@author Andreas Reiss
 *@version 1.0
 * <br>Date: 11.08.2003
 * <br>Time: 15:54:20
 * <br> copyright Methodyne GmbH, Zug - Switzerland.
*/

public class DataLayer {

    String pkg;
    String objects;

    public MemStore memStore ;

    /**
     * Creates and initialises a new in memory datastore
     * @param dir the directory to persist data on disk
     */
    public DataLayer(String dir) {
        pkg = "net.methodyne.bellvue.system";
        objects = pkg + ".User," + pkg + ".Group," + pkg + ".Application," + pkg + ".BClass," + pkg + ".Bulk";// + pkg + ".ClipObject" ;

        try {
            memStore = new MemStore(new DataKeeper(objects), dir ,3 );
        } catch (IOException e) { e.printStackTrace();
        } catch (ClassNotFoundException e) {  e.printStackTrace(); }
        System.out.println("DataLayer:new DataLayer!");
        Sdata sd = new Sdata();
        sd.user = new User();
        Group g = new Group();
        g.groupname = "admin";
        sd.user.groups.add(g);
        new DataInit().createSystem( sd, this, objects);
        init();
    }

    /**
     * Initialise supporting Hashes
     */
    public void init(){
        memStore.system().initMaps();
    }

    /**
     * Used by the bulk object only - do not use this in your application!
     * @param sd
     * @param x
     * @param bulk
     */
    public void store(Sdata sd, Object x, boolean bulk) {
        System.out.println("DataLayer:insert:bulk");
        try {
            long currentid = ( memStore.system()).getId(sd, x.getClass().getName() );
            //System.out.println("new Id: " + newid);
            long xid = x.getClass().getField("id").getLong(x);
            if( currentid <= xid ){
                memStore.executeCommand(sd, new DataSetIdCommand( x.getClass().getName() , xid ));
            }
            memStore.executeCommand(sd, new DataInsertCommand( x ));
        } catch (Exception e) { e.printStackTrace();  }
    }

    /**
     * Store an object without setting a lock.
     * @param sd
     * @param x
     */
    public void storeUnlocked(Sdata sd, Object x) {
        System.out.println("DataLayer:storeUnlocked");
        try {
            if( x.getClass().getField("id").getLong(x) < 0 ){ // object needs valid id
                long newid = ( memStore.system()).getId(sd, x.getClass().getName() );
                //System.out.println("new Id: " + newid);
                x.getClass().getField("id").setLong(x, newid);
                memStore.executeCommand(sd, new DataSetIdCommand( x.getClass().getName() , newid ));
                memStore.executeCommand(sd, new StoreUnlockedCommand(x));
            }
            else{   // object has valid id
                memStore.executeCommand(sd, new DataUpdateCommand(x));
            }
        } catch (Exception e) { e.printStackTrace();  }
    }

    /**
     * Store an object and set the lock for the user in sd.user
     * @param sd
     * @param x
     */
    public void store(Sdata sd, Object x) {
        Monitor store = MonitorFactory.start("DataLayer:insert");

        //System.out.println("DataLayer:insert");
        try {
            if( x.getClass().getField("id").getLong(x) < 0 ){ // object needs valid id
                long newid = ( memStore.system()).getId(sd, x.getClass().getName() );
                //System.out.println("new Id: " + newid);
                x.getClass().getField("id").setLong(x, newid);
                memStore.executeCommand(sd, new DataSetIdCommand( x.getClass().getName() , newid ));
                memStore.executeCommand(sd, new DataInsertCommand(x));
            }
            else{   // object has valid id
                memStore.executeCommand(sd, new DataUpdateCommand(x));
            }
        } catch (Exception e) { e.printStackTrace();  }
        store.stop();
    }

    /**
     * Delete
     * @param sd
     * @param x
     */
    public void delete(Sdata sd, Object x) {
        Monitor del = MonitorFactory.start("DataLayer:del");
        try {
            memStore.executeCommand(sd, new DataDeleteCommand(x));
        } catch (Exception e) { e.printStackTrace();  }
        del.stop();
    }

    /**
     * Retrieve data from the store via the given Finder
     * @param x
     * @param sd
     * @return the given finder
     */
    public Finder fetch(Finder x, Sdata sd){
        Monitor fet = MonitorFactory.start("DataLayer:fetch");
        Finder dd = ( memStore.system()).fetch(x, sd);
        fet.stop();
        return dd;
    }

    /**
     * Retrieves an object without locking it.
     * @param sd the session data
     * @param name of the Bclass
     * @param reference the id
     * @param unlocked
     * @return The object with the given reference (id) or null.
     */
    public Object retrieve(Sdata sd, String name, long reference, boolean unlocked){
        return getRefObjects(( memStore.system()).retrieve(sd, name,reference, unlocked),sd);
    }

    /**
     * Retrieves an object and locks it. Like a get by id
     * @param sd
     * @param name
     * @param reference
     * @return The object with the given reference (id) or null.
     */
    public Object retrieve(Sdata sd, String name, long reference){
        Monitor ret = MonitorFactory.start("DataLayer:retrieve");
        Object o = ( memStore.system()).retrieve(sd, name,reference);
        ret.stop();
        return getRefObjects(o, sd);
    }

    /**
     * To enable navigation from one object to the next in store.
     * @param sd
     * @param x
     * @return the object with the next higher id
     */
    public Object retrieveNext(Sdata sd, Object x){
        Monitor retrieveNext = MonitorFactory.start("DataLayer:retrieveNext");
        Object o = ( memStore.system()).retrieveNext(sd, x);
        retrieveNext.stop();
        return getRefObjects(o, sd);
    }

    /**
     * To enable navigation from one object to the next in store.
     * @param sd
     * @param x
     * @return the object with the next lower id
     */
    public Object retrievePrevious(Sdata sd, Object x){
        Monitor retrievePrevious = MonitorFactory.start("DataLayer:retrievePrevious");
        Object o = ( memStore.system()).retrievePrevious(sd, x);
        retrievePrevious.stop();
        return getRefObjects(o, sd);
    }

    public void addAll(Sdata sd, Collection c, String name ){
        ( memStore.system()).addAll(sd, c, name);
    }

    public void repAll(Sdata sd, Collection c, String name ){
        ( memStore.system()).repAll(sd, c, name);
    }

    /**
     * Dumps the data from memory to disk. This gets done periodically by default.
     * <br> The interval can be configured in web.xml with StateWriteScheduleSeconds in the MainServlet section.
     * <br>The data gets serialized and compressed with gzip on the fly for efficiency.
     */
    public void takeState() {
        Monitor takeState = MonitorFactory.start("DataLayer: write state");
        try {
            //System.out.println( new Date() + " Taking state");
            memStore.writeState();
        } catch (IOException iox) { System.out.println("Write State failed: " + iox.getMessage());}
        takeState.stop();
    }

    /**
     * Count all objects in the Store
     * @return
     */
    public long getObjectCount() {
        return ( memStore.system()).getObjectCount();
    }

    /**
     * Checks is the user in sd.user is allowed to access the Bclass given by name
     * @param sd session data
     * @param name Bclass name
     * @return true if permitted, false otherwise
     */
    public boolean permitted(Sdata sd, String name) {
        return memStore.system().permitted(sd, name);
    }

    /**
     * Explicitly unlocks an object. This has to be called when finished with an object or
     * it will be locked for others.
     * @param o object to unlock
     * @param sd the session data
     */
    public void unlock(Object o, Sdata sd) {
        memStore.system().doLock(o, sd, false);
    }

    /** This attempts to put a lock for this object and returns the locktype
     *
     * @param y object to check
     * @param sd
     * @return
     * <br>   0 EXRW locker = this user locked it in mode exclusive read and write
     * <br>   1 EXRW other = object is locked by another user
     * <br>   2 EXW locker = this user locked it in mode exclusive write
     * <br>   3 EXW other = object is locked by another user
     * <br>   4 OFF = locking is turned off for this bclass
     * <br>   5 unknown object type
     */
    public int lockType(Object y, Sdata sd) {
        return memStore.system().lockType( y,sd );
    }

    /**
     * Used to restore the referenced objects in collections or references.
     * @param x
     * @param sd
     * @return the given object
     */
    public Object getRefObjects(Object x, Sdata sd) {
        if(x == null) return null;
        Field[] f = x.getClass().getFields();
        if (x.getClass().getName().endsWith(".Finder")) {
            fetch((Finder) x, sd);
        } else {
            for (int i = 0; i < f.length; i++) {
                try {
                    String name = f[i].getName();
                    if (f[i].getType().getName().endsWith(".Vector") && (!name.startsWith("reference"))) {
                        //System.out.println(name);
                        String type = invoke(x, typeName(name));
                        Vector r = (Vector) x.getClass().getField(refName(name)).get(x);
                        if (r.size() > 0) {
                            Vector v = new Vector();
                            for (int j = 0; j < r.size(); j++) {
                                Object a = r.elementAt(j);
                                if (a.getClass().getName().endsWith("Long")) {
                                    Object d = retrieve(sd, type, ((Long) a).longValue(),true);
                                    if (d != null) {
                                        v.add(d);
                                    } else {
                                        r.removeElementAt(j);
                                    }
                                }
                            }
                            x.getClass().getField(name).set(x, v);
                        }
                    } else if (f[i].getType().getName().endsWith(".Object") && (!name.startsWith("reference"))) {
                        //System.out.println(name);
                        String type = invoke(x, typeName(name));
                        Object a = x.getClass().getField(refName(name)).get(x);
                        if (a != null) {
                            Object d = retrieve(sd, type, ((Long) a).longValue(), true);
                            if (d != null) {
                                x.getClass().getField(name).set(x, d);
                            } else {
                                x.getClass().getField(refName(name)).set(x, null);
                            }
                        }
                    }
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                }
            }
        }
        return x;
    }

    private String invoke(Object x, String sn) {
        String doit = "";
        try {
            doit = (String) x.getClass().getMethod(sn, null).invoke(x, null);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        return doit;
    }

    String typeName(String name) {
        return "type" + name.substring(0, 1).toUpperCase() + name.substring(1);
    }

    String refName(String name) {
        return "reference" + name.substring(0, 1).toUpperCase() + name.substring(1);
    }


}