package pers.hal42.database;

import pers.hal42.lang.ReflectX;
import pers.hal42.logging.ErrorLogStream;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ListIterator;

/**
 * Created by Andy on 7/12/2017.
 * <p>
 * create objects from table by annotation assisted reflection.
 * todo:0 use an annotation on the class to supply 'local' option.
 */
public class TableFiller {
  private static final ErrorLogStream dbg = ErrorLogStream.getForClass(TableFiller.class);

  /**
   * parse result set row into a new object and append to @param list. @param ref is the class of the type and reflection looks for @DbColumn annotations to decide what to match. At the moment ref must have a no-args constructor.
   *
   * @returns the number of added fields. If the value is negative then it is the complement of how many were added before an exception halted the process.
   */
  public static int addToList(ResultSet rs, Class ref, boolean local, ListIterator list) {
    int successes = 0;
    DbColumn all = ReflectX.getAnnotation(ref, DbColumn.class);
    try {
      while (rs.next()) {//for each row in result set
        Object obj = ref.newInstance();
        ReflectX.FieldWalker fields = new ReflectX.FieldWalker(ref, local);//this line is expensive enough that we should make a rewindable list.
        fields.forEachRemaining(field -> {
            DbColumn colAttr = field.getAnnotation(DbColumn.class);
            if (colAttr != null || (all != null && !ReflectX.ignorable(field))) {
              try {
                field.setAccessible(true);//be aggressive, but only useful if we are doing local walking.
                String name = field.getName();
                Class fclaz = (field.getType());
                //can't switch on class types <sad/>
                if (fclaz == String.class) {
                  field.set(obj, rs.getString(name));
                } else if (fclaz == boolean.class) {
                  field.setBoolean(obj, rs.getBoolean(name));
                } else if (fclaz == double.class) {
                  field.setDouble(obj, rs.getDouble(name));
                } else if (fclaz == int.class) {
                  field.setInt(obj, rs.getInt(name));
                } else if (fclaz.isEnum()) {
                  int enumMod = colAttr.size();
                  if (enumMod == 0) {//if field is integer then index into enum constants
                    int ordinal = rs.getInt(name);
                    field.set(obj, ReflectX.enumByOrdinal(fclaz, ordinal));
                  } else { //else if text then somehow the fclaz must give us a value.
                    //want a static method that takes a string and gives an fclaz.
                    field.set(obj, ReflectX.enumObject(fclaz, rs.getString(name)));
                  }
                } else {
                  dbg.ERROR("Not yet parsing type {0} from result set (field named {1})", fclaz.getName(), name);
                }
              } catch (IllegalAccessException | SQLException e) {
                dbg.Caught(e);
              }
            }
          }
        );
        list.add(obj);
        ++successes;
      }
      return successes;
    } catch (SQLException e) {
      dbg.Caught(e, "iterating to create objects by reflection");
      return ~successes;
    } catch (IllegalAccessException | InstantiationException e) {
      dbg.Caught(e);
      return ~successes;
    }
  }

  public static void loadTable(String query, Class infoClass, boolean local, ListIterator all, DBMacros jdbc) {
    jdbc.doSimpleQuery(query, rs -> addToList(rs, infoClass, local, all));
  }
}

@@ -186,16 +186,53 @@
 terator;
+%0Aimport java.util.function.Predicate;
 %0A%0A/**%0A *
@@ -941,16 +941,20 @@
 c static
+ %3CT%3E
  int add
@@ -979,16 +979,29 @@
 s, Class
+%3C? extends T%3E
  ref, bo
@@ -1025,21 +1025,49 @@
 Iterator
+%3CT%3E
  list
+, Predicate%3CT%3E refinclude
 ) %7B%0A    
@@ -1169,25 +1169,10 @@
     
-while (rs.next())
+do
  %7B//
@@ -1197,16 +1197,55 @@
 sult set
+, which has already been 'nexted' once.
 %0A       
@@ -1245,22 +1245,17 @@
         
-Object
+T
  obj = r
@@ -1349,16 +1349,23 @@
 ocal);//
+todo:1 
 this lin
@@ -2363,16 +2363,35 @@
 isEnum()
+ && colAttr != null
 ) %7B%0A    
@@ -3232,16 +3232,76 @@
      );%0A
+        if (refinclude == null %7C%7C refinclude.test(obj)) %7B%0A  
         
@@ -3315,16 +3315,18 @@
 d(obj);%0A
+  
         
@@ -3340,25 +3340,54 @@
 sses;%0A      
-%7D
+  %7D%0A      %7D while (rs.next());
 %0A      retur
@@ -3659,16 +3659,20 @@
 c static
+ %3CT%3E
  void lo
@@ -3698,16 +3698,29 @@
 y, Class
+%3C? extends T%3E
  infoCla
@@ -3750,16 +3750,238 @@
 Iterator
+%3CT%3E all, DBMacros jdbc) %7B%0A    loadTable(query, infoClass, local, null, all, jdbc);%0A  %7D%0A%0A  public static %3CT%3E void loadTable(String query, Class%3C? extends T%3E infoClass, boolean local, Predicate%3CT%3E refinclude, ListIterator%3CT%3E
  all, DB
@@ -4066,16 +4066,28 @@
 cal, all
+, refinclude
 ));%0A  %7D%0A

fix "10k" refcon mismatch issue. was missing first item in state table.