#include "shared_grade_book_calculation_util.h"
#include "shared_util.h"

GRADE_BOOK_CONFIGURE global_grade_conf;


static void get_min_max_avg(GRADE_LIST *list, ITEM *item)
{
   double total =0;
   double small = 9999;
   double big = -9999; 
   int count =0;
   GRADE *ptr;

   for(ptr = list->head; ptr; ptr = ptr->next)
   {
      if(ptr->score >= 0)
      {
	 if(ptr->score < small)
		 small = ptr->score;
	 if(ptr->score > big)
		 big = ptr->score;
	 total += ptr->score;
         count++;

      } 
   }
   if(count >0)
   {
     snprintf(item->min, MAX_GRADE_LEN +1, "%4.*f", test_precision(small), small);
     snprintf(item->max, MAX_GRADE_LEN +1, "%4.*f", test_precision(big), big);
     snprintf(item->avg, MAX_GRADE_LEN +1, "%4.*f", test_precision(total/(float)count), total/(float)count);
   }
}

static double look_up_points(int letter_id, LETTER_GRADE_LIST *list)
{
    LETTER_GRADE *ptr;

    for(ptr = list->head; ptr; ptr = ptr->next)
    {
	 if(ptr->letter_id == letter_id)
		return ptr->points;
    }
    return -1;
}

static void points_to_letter(double points, LETTER_GRADE_LIST *list, char *letter)
{
    LETTER_GRADE *next;
    LETTER_GRADE *ptr;

    for(ptr = list->head; ptr; ptr = ptr->next)
    {
          next = ptr->next;
	  if(next)
	  {
            if(points <= ptr->points && points > next->points)
	    {
	     strncpy(letter, ptr->letter, MAX_GRADE_LEN +1);	
            	     return;
	    }
        }
        else
	{
	     strncpy(letter, ptr->letter, MAX_GRADE_LEN +1);	
            	     return;
	} 
    }
   
}



static void get_letter_min_max_avg(ITEM *item)
{
    double total =0;
    double small =9000000;
    double big =-9000000;
    double points =0;
    int count = 0;
    GRADE *ptr;
    for(ptr = item->grades->head; ptr;ptr = ptr->next)
    {
       if(ptr->letter_id > 0)
       {
          points = look_up_points(ptr->letter_id, item->letters);
	  if(points < small)
	     small = points;
	  if(points > big)
	     big = points;
	 total += points; 
	 count++;
       }
    }
    if(count >0 && total >=0 )
    {
      points_to_letter(small, item->letters, item->min);
      points_to_letter(big, item->letters, item->max);
      points_to_letter(total/(double)count, item->letters, item->avg);
    }
}


void get_items_min_avg_max(CONFIG_STRUCT *conf, const char *my_db)
{

   ITEM *ptr;


   if(global_item_list && global_item_list->head)
   {
        for(ptr = global_item_list->head; ptr; ptr = ptr->next)
	{

	    if(strcmp(ptr->grade_entered, "yes") ==0)
	    {
		if(strcmp(ptr->is_letter_grade, "yes") == 0)
		{
                   get_letter_grades(conf, ptr->item_id, my_db);
            	   ptr->letters = global_letter_grades;
		   get_letter_min_max_avg(ptr);
		}
		else
		{
		  if(ptr->grades)
		  {
                     get_min_max_avg(ptr->grades, ptr);		      
		  }
		}
	    }
	}
   }
  
}
static double get_max_letter_points(LETTER_GRADE_LIST *list)
{
    double max = 0;
    LETTER_GRADE *ptr;

    for(ptr = list->head; ptr; ptr = ptr->next)
    {
	if(ptr->points > max)
		max = ptr->points;
    }
    return max;
    
}

void  get_one_student_grade(double total, char *grade)
{
    SCALE *ptr;

    if(global_scale_list && global_scale_list->head)
    {
	for(ptr = global_scale_list->head;ptr;ptr = ptr->next)
	{
	    if(total >= ptr->start &&
			    total < ptr->end)
		    strncpy(grade, ptr->grade, MAX_GRADE_LEN +1);

	}
    } 
}

static int  find_number_graded_items_for_one_user(int cat_id, TOTAL_GRADE *one_student)
{
   GRADE *grades;
   int count = 0;
   ITEM *ptr;

   for(ptr = global_item_list->head; ptr; ptr = ptr->next)
   {
	 if(ptr->cat_id == cat_id && strcmp(ptr->extra_credit, "no") ==0 && strcmp(ptr->grade_entered, "yes") ==0)
	 {
	    if(ptr->grades)
	    {
               for(grades = ptr->grades->head; grades; grades = grades->next)
               {
	             if(strcmp(grades->username, one_student->username) == 0)
		     {
                          if(strcmp(grades->pending, "no") == 0 && strcmp(grades->excuse, "no") ==0)
			      count++;
		     }
	       }
	    }
		          
	 }
  }
  return count;
}

static int get_lowest_item_id(TOTAL_GRADE *one_student, int cat_id)
{
     double low = 9000000;
     double temp; 
     ITEM *ptr;
     GRADE *grades;
     double letter_score;
     double letter_points;
     int item_id = -1;


       for(ptr = global_item_list->head;ptr; ptr = ptr->next)
       {
	    if(ptr->cat_id == cat_id && strcmp(ptr->hide, "no") ==0)
	    {
	       if(ptr->grades)
	       {
                 for(grades = ptr->grades->head; grades; grades = grades->next)
                 {
	             if(strcmp(grades->username, one_student->username) == 0)
		     {
                          if(strcmp(grades->pending, "no") == 0 && strcmp(grades->excuse, "no") ==0 
					  && strcmp(grades->drop, "no") ==0 && strcmp(ptr->extra_credit, "no") ==0)
			  {

	                     if(strcmp(ptr->is_letter_grade, "no") == 0)
			     {
				     temp = grades->score / ptr->points; 
				     if(temp < low)
				     {
					  low = temp;
					  item_id = grades->item_id;
				     }
			     }
			     else
			     {
                                  letter_score = look_up_points(grades->letter_id, ptr->letters);
	                          letter_points = get_max_letter_points(ptr->letters);
                                  temp = letter_score / letter_points;
				  if(temp < low)
				  {
				      low = temp;
				      item_id = grades->item_id;
				  }

			     }


			  }

		     }
	       }
	    }
		          
	 }
      }
      return item_id;
}	

static void  update_score_list(TOTAL_GRADE *one_student, int low_id)
{
   ITEM *ptr;
   GRADE *grades;
   char name[MAX_PATH];


    for(ptr = global_item_list->head;ptr; ptr = ptr->next)
    {
	    if(ptr->item_id == low_id)
	    {
	       if(ptr->grades)
	       {
		 for(grades = ptr->grades->head; grades; grades = grades->next)
		 {
	              if(strcmp(one_student->username, grades->username) ==0)
		      {
			    strncpy(grades->drop, "yes", MAX_BOOLEN_LEN +1);
			    snprintf(name, MAX_PATH, "score.%s.%d.drop", grades->username, ptr->item_id);
			    cs_set_value(name, grades->drop);
			    return;
		      }
		 }
	       }
	    }
    }

}

void mark_drop_lowest_scores(int cat_id, TOTAL_GRADE *one_student, int number_drop)
{
   int i;
   int low_id;

   for(i =0; i<number_drop; i++)
   {
      low_id = get_lowest_item_id(one_student, cat_id);
      if(low_id > 0)
          update_score_list(one_student, low_id);
   }
 
}

static void update_drop_lowest_score(TOTAL_GRADE *one_student)
{
   int count = 0;
   CATEGORY *ptr;


    for(ptr = global_cat_list->head;ptr; ptr = ptr->next)
    {
	 count =0; 

	 if(ptr->drop_lowest > 0 && ptr->number_graded_items >= ptr->drop_lowest +1)
	 {
	     count = find_number_graded_items_for_one_user(ptr->cat_id, one_student);

	     if(count >= ptr->drop_lowest +1)
	     {
                 mark_drop_lowest_scores(ptr->cat_id, one_student, ptr->drop_lowest);
	     }
              
	 }

    } 
}



static void get_basic_points(TOTAL_GRADE *one_student)
{
   double total =0;
   double total_base =0;
   GRADE *grades;
   ITEM *ptr;
   CATEGORY *cat_ptr;
   char temp[MAX_PATH +1];
   int count =0;

  for(cat_ptr = global_cat_list->head; cat_ptr; cat_ptr = cat_ptr->next)
  {
   if(cat_ptr->weight > 0)
   {
     for(ptr = global_item_list->head;ptr; ptr = ptr->next)
     {
       if(strcmp(ptr->hide, "no") ==0 && ptr->grades && cat_ptr->cat_id == ptr->cat_id)
       {

         for(grades = ptr->grades->head; grades; grades = grades->next)
	 {
	      if(strcmp(grades->username, one_student->username) == 0)
	      {

                 if(strcmp(grades->pending, "no") == 0 && strcmp(grades->excuse, "no") ==0 && strcmp(grades->drop, "no") == 0)
		 {
	              snprintf(temp, MAX_PATH +1, "has_total.%s", grades->username);
	              cs_set_int_value(temp, 1);
		      count++;
	             if(strcmp(ptr->is_letter_grade, "no") == 0)
	             {
                         total += grades->score;
			 if(strcmp(ptr->extra_credit, "no") == 0 )
	                         total_base += ptr->points;
		     }
		     else
		     {

	                 if(ptr->letters)
		         {
                            total += look_up_points(grades->letter_id, ptr->letters);
			    if(strcmp(ptr->extra_credit, "no") == 0)
	                       total_base += get_max_letter_points(ptr->letters);
		         }
		     }
		     
		 }
	      }

	 }
       }
     }
    }
   }
  
   if(total_base > 0 && total >0 && count)
   { 
    if(global_grade_conf.rounding_method == WHOLE_NUMBER)
        one_student->total = whole_number(total /total_base *100);
    else
        one_student->total = nearest_tenth(total /total_base *100);

   get_one_student_grade(one_student->total, one_student->grade);
  }
  else if(total >=0 && count) 
  {
    if(global_grade_conf.rounding_method == WHOLE_NUMBER)
        one_student->total = whole_number(total);
    else
        one_student->total = nearest_tenth(total);

   get_one_student_grade(one_student->total, one_student->grade);
  }

}

static double  get_one_cat_total(TOTAL_GRADE *one_student, int cat_id, int *base_has_value)
{
   double total = 0;
   double total_base = 0;
   GRADE *grades;
   ITEM *ptr;
   char temp[MAX_PATH +1];
   int count =0;

   for(ptr = global_item_list->head;ptr; ptr = ptr->next)
   {
       if(ptr->cat_id == cat_id && strcmp(ptr->hide, "no") == 0)
       {
         if(ptr->grades)
         {

            for(grades = ptr->grades->head; grades; grades = grades->next)
	    {
	        if(strcmp(grades->username, one_student->username) == 0)
	        {
                      if(strcmp(grades->pending, "no") == 0 && strcmp(grades->excuse, "no") ==0 && strcmp(grades->drop, "no") == 0)
		      {
			   count++;
	                   snprintf(temp, MAX_PATH +1, "has_total.%s", grades->username);
	                   cs_set_int_value(temp, 1);
	                   if(strcmp(ptr->is_letter_grade, "no") == 0)
	                   {
                               total += grades->score;
			       if(strcmp(ptr->extra_credit, "no") == 0 )
	                         total_base += ptr->points;
		           }
		           else
		           {

	                        if(ptr->letters)
		                {
                                   total += look_up_points(grades->letter_id, ptr->letters);
			           if(strcmp(ptr->extra_credit, "no") == 0)
	                              total_base += get_max_letter_points(ptr->letters);
		                }
		           }
		     
		      }
	         }
             }
	  }
       }
   }
   if(total > 0 && total_base > 0 && count)
   {
      *base_has_value = 1;
      return  total / total_base;
   }
   else if(total >= 0 && count)
   {
      *base_has_value = 0;
      return total;
   }
   else
   {
      *base_has_value = 0;
	return -99999;
   }
   
       
}


static void get_category_weight(TOTAL_GRADE *one_student)
{
     double one_cat_total =0;
     double total =0;
     CATEGORY *ptr;
     int base_has_value = 0;
     int count =0;

     for(ptr = global_cat_list->head; ptr; ptr = ptr->next)
     {
	  if(ptr->weight >0)
	  {
	    one_cat_total = get_one_cat_total(one_student, ptr->cat_id, &base_has_value);
	    if(one_cat_total >=0 && base_has_value)
	    {
	       total += one_cat_total * ptr->weight;
	       count++;
	    }
	    else if(one_cat_total >=0)
	    {
	       total += one_cat_total;
	       count++;
	    }
	  }
     }
   
    if(total >=0 && count)
    { 
     if(global_grade_conf.rounding_method == WHOLE_NUMBER)
        one_student->total = whole_number(total);
     else
        one_student->total = nearest_tenth(total);
     get_one_student_grade(one_student->total, one_student->grade);
    } 

}

static void get_user_total_grade(TOTAL_GRADE *one_student)
{
   
   update_drop_lowest_score(one_student);

   if(global_grade_conf.scoring_method == BASIC_POINTS)
	 get_basic_points(one_student);
   else
         get_category_weight(one_student);

}

TOTAL_GRADE *calculate_total_grades(P_NODE *person_list, CONFIG_STRUCT *conf, const char *my_db)
{
   TOTAL_GRADE *head =0, *current=0, *ptr;
   P_NODE *p_ptr;

   if(!read_grade_book_conf(conf, &global_grade_conf))
   {
	cs_set_int_value("should_set_cal_method", 1);
	return head;
   }

   if(global_grade_conf.scoring_method == NO_CAL)
   {
       cs_set_value("calculation_method", "no_cal");
       return head;
   }
   else if(global_grade_conf.scoring_method == BASIC_POINTS)
       cs_set_value("calculation_method", "basic_points");
   else
       cs_set_value("calculation_method", "category_weight");


   get_scales(conf, my_db);


   for(p_ptr = person_list; p_ptr; p_ptr = p_ptr->next)
   {
	if(p_ptr->data.group != FACULTY && *(p_ptr->data.realname) != '*')
	{
            ptr = (TOTAL_GRADE*) malloc(sizeof(TOTAL_GRADE));
            if(!ptr)
	       cs_critical_error(ERR_MALLOC_FAILED, "calculate total grades");
            strncpy(ptr->username, p_ptr->data.username, MAX_USERNAME +1);
            ptr->total = -99999;
            ptr->next =0;
            
	    get_user_total_grade(ptr);
	    if(!head)
		 head =  ptr;
	    else
		current->next = ptr;
	    current = ptr;
	}

   } 
   return head; 

}
  
double whole_number(double x)
{
    double temp;

    temp = x - (int)x;

    if(temp >= 0.5)
         return ceil(x);
    else
        return floor(x);
}

double nearest_tenth(double x)
{
     double temp, temp_x;
     temp_x = x * 10;

     temp = whole_number(temp_x);
     return temp /10;
}


