package kz.cafe.db;

import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.*;
import kz.cafe.constants.*;
import kz.cafe.db.model.*;
import kz.cafe.exceptions.*;
import org.apache.commons.logging.*;
import com.google.code.morphia.*;
import com.google.code.morphia.query.*;
import com.mongodb.*;

public class MongoService{
   private static Log log = LogFactory.getLog(MongoService.class);
   private static final String LOG = "[MONGO] ";

   public static final Datastore ds;
   
   
   private static final ConcurrentHashMap<Class<?>, String[]> mapIndexFields = new ConcurrentHashMap<Class<?>, String[]>();
   


   static{
      log.info(LOG + "Connecting to DB...");
      
      try{
         Morphia morphia = new Morphia();
         morphia.mapPackage("kz.cafe.db.model");

         Mongo db = new Mongo(ServerConstants.getMongoURL());
         ds = morphia.createDatastore(db, ServerConstants.getDatabase());

         ds.ensureIndexes();
         
         
         MCounterRequests mCounterSystem = ds.find(MCounterRequests.class).get();
         if(mCounterSystem == null){
            mCounterSystem = new MCounterRequests();

            try{
               ds.save(mCounterSystem);
            }
            catch(MongoException.DuplicateKey e){
               mCounterSystem = ds.find(MCounterRequests.class).get();
            }
         }
         
      }
      catch (Exception e){
         throw new Error("Can not connect to Mongo", e);
      }
      
      
      log.info(LOG + "ok.");
   }
   
   
   
   public static void save(MAbstractEditableDataElement obj){
      log.trace(LOG + obj);
      String[] arrstrIndexFields = getHiddenFields(obj.getClass());
      
      Class<? extends MAbstractEditableDataElement> class_ = obj.getClass();
      
      for(String sHiddenFieldName : arrstrIndexFields){
         try{
            Field fieldValue = class_.getField(sHiddenFieldName.substring(0, sHiddenFieldName.length() - 1));
            Field fieldIndex = class_.getField(sHiddenFieldName);
            
            Object objValue   = fieldValue.get(obj);
            Object objToIndex = fieldIndex.get(obj);
            
            if(objValue instanceof Map){
               @SuppressWarnings("unchecked")
               Map<InterfaceConstants.LANGS, String> mapValue = (Map<InterfaceConstants.LANGS, String>) objValue;
               @SuppressWarnings("unchecked")
               Map<InterfaceConstants.LANGS, String> mapIndex = (Map<InterfaceConstants.LANGS, String>) objToIndex;
               
               for(InterfaceConstants.LANGS lang : InterfaceConstants.LANGS.values()) {
                  mapIndex.put(lang, mapValue.get(lang).toLowerCase());
               }
            }
            else{
               fieldIndex.set(obj, ("" + objValue).toLowerCase());
            }
         }
         catch (Exception e){
         }
      }
      
      
      log.trace(LOG + "save() " + obj);
      
      obj.updated = System.currentTimeMillis();
      
      ds.save(obj);
      
      log.trace(LOG + "ok");
   }
   
   
   
   
   public static <T> T get(Class<T> clas, int ID){
      return MongoService.ds
                         .find(clas)
                         .filter(MAbstractDescriptor.F_ID, ID)
                         .filter(MAbstractEditableDataElement.F_DELETED, false)
                         .retrievedFields(false, getHiddenFields(MReservation.class))
                         .get();      
   }
   

   
   
   public static <T> T getExceptionable(Class<T> clas, int ID) throws AbstractException{
      T t = get(clas, ID);
      
      if(t == null){
         throw new ObjectNotFoundException(clas, ID);
      }
      else {
         return t;
      }
   }
   
   
   
   
   
   
   public static<T> List<T> getList(Class<T> classToLoad){
      return MongoService.ds
                         .find(classToLoad)
                         .filter(MAbstractEditableDataElement.F_DELETED, false)
//                         .order(MAbstractEditableDataElement.F_ID)
                         .retrievedFields(false, getHiddenFields(classToLoad))
                         .asList();      
   }
   

   
   public static<T> List<T> getList(Class<T> classToLoad, Constraint constraint){
      Query<T> query = MongoService.ds
                                   .find(classToLoad)
                                   .filter(MAbstractEditableDataElement.F_DELETED, false)
//                                 .order(MAbstractEditableDataElement.F_ID)
                                   .retrievedFields(false, getHiddenFields(classToLoad));
   
      for(Constraint.Criterion criterion : constraint.criteria) {
         query.filter(criterion.field, criterion.value);
      }
      
      return query.asList();
   }
   
   
   

   
   @SuppressWarnings({ "unchecked", "rawtypes" })
   public static Object getListFrontEnd(Class classToLoad, long lTime){
      return MongoService.ds.find(classToLoad)
                            .filter(MAbstractEditableDataElement.F_UPDATED + " >=", lTime)
                            .retrievedFields(false, getHiddenFields(classToLoad))
                            .asList();      
   }
   public static List<MReservation> getReserves(int UID, long lTime){
      return MongoService.ds.find(MReservation.class)
                            .filter(MReservation.F_UID, UID)
                            .filter(MAbstractEditableDataElement.F_UPDATED + " >=", lTime)
                            .retrievedFields(false, getHiddenFields(MReservation.class))
                            .asList();      
   }
   
   

   
   
   
   
   
   
   public static<T> void undelete(Class<T> classToUndelete, int ID){
      Query<T> q = ds.createQuery(classToUndelete).filter(MAbstractDataElement.F_ID, ID);
      UpdateOperations<T> uOps = ds.createUpdateOperations(classToUndelete).set(MAbstractDataElement.F_DELETED, false);
      ds.findAndModify(q, uOps);
   }
   
   
   
   
   
   
   
   
   
   public static long getUniqueRequestID(){
      Query<MCounterRequests> q = ds.createQuery(MCounterRequests.class);

      UpdateOperations<MCounterRequests> uOps =
                       ds.createUpdateOperations(MCounterRequests.class).inc(MCounterRequests.NEXT);

      MCounterRequests mSystem = ds.findAndModify(q, uOps);
      return mSystem.counterRequests;
   }



   
   
   


   @SuppressWarnings("rawtypes")
   public static int getUniqueID(Class class_){
      Query<MCounterModel> q =
            ds.createQuery(MCounterModel.class).filter(MCounterModel.CLASS, class_.getSimpleName());
      
      MCounterModel mCounter = q.get();
      if(mCounter == null){
         mCounter = new MCounterModel(class_.getSimpleName());

         try{
            ds.save(mCounter);
         }
         catch(MongoException.DuplicateKey e){
         }
      }
      

      UpdateOperations<MCounterModel> uOps =
                            ds.createUpdateOperations(MCounterModel.class).inc(MCounterModel.NEXT);

      MCounterModel mCounterUser = ds.findAndModify(q, uOps);
      return mCounterUser.nextFreeID;
   }
   
   
   
   
   private static String[] getHiddenFields(Class<?> class_){
      String[] arrstrIndexFields = mapIndexFields.get(class_);
      
      if(arrstrIndexFields == null){

         Set<String> setHiddenFields = new HashSet<String>();

         for(Field field : class_.getFields()){
            if(field.getName().endsWith("_")){
               setHiddenFields.add(field.getName());
            }
         }
         
         arrstrIndexFields = setHiddenFields.toArray(new String[setHiddenFields.size()]);
         mapIndexFields.put(class_, arrstrIndexFields);
      }

      return arrstrIndexFields;
   }
}
