import { GraphQLObjectType, GraphQLString, GraphQLFloat, GraphQLList } from 'graphql'
import { instructorType } from './instructor';
import { courseType } from './course';

import { getInstructor, getUCINetIDFromName } from '../helpers/instructor.helper';
import { getCourse } from '../helpers/courses.helper';
import { ValidationError } from '../helpers/errors.helper';

const meetingType: GraphQLObjectType = new GraphQLObjectType({
  name: "Meeting",

  fields: () => ({
    building: { 
      type: GraphQLString,
      resolve: (meeting) => {
        return meeting["bldg"];
      }
    },
    days: { type: GraphQLString },
    time: { type: GraphQLString }
  })
});
  
const sectionInfoType: GraphQLObjectType = new GraphQLObjectType({
  name: "SectionInfo",
  
  fields: () => ({
    code: { type: GraphQLString },
    comment: { type: GraphQLString },
    number: { type: GraphQLString },
    type: { type: GraphQLString }
  })
});

const courseOfferingType: GraphQLObjectType = new GraphQLObjectType({
  name: "CourseOffering",

  fields: () => ({
    year: { type: GraphQLString },
    quarter: { type: GraphQLString },
    instructors: { 
      type: new GraphQLList(instructorType),
      resolve: (offering) => {
        return offering.instructors.map((name: string) => {
          
          //Fetch all possible ucinetids from the instructor.
          let ucinetids : string[] = getUCINetIDFromName(name);
          
          //If only one ucinetid exists and it's in the instructor cache, 
          //then we can return the instructor for it.
          if (ucinetids && ucinetids.length == 1) { 
            const instructor = getInstructor(ucinetids[0]);
            if (instructor) { return instructor; }
          }
          //If there is more than one and the course exists, 
          //use the course to figure it out.
          else if (ucinetids && ucinetids.length > 1 && offering.course) {

              //Filter our instructors by those with related departments.
              let course_dept = offering.course.department;
              let instructors = ucinetids.map(id => getInstructor(id)).filter( temp => temp.related_departments.includes(course_dept));
              
              //If only one is left and it's in the instructor cache, we can return it.
              if (instructors.length == 1) {
                return instructors[0];
              } else {
                //Filter instructors by those that taught the course before.
                instructors = instructors.filter( inst => {
                  return inst.course_history.map((course) => course.replace(/ /g, "")).includes(offering.course.id);
                });
              
                //If only one is left and it's in the instructor cache, we can return it.
                if (instructors.length == 1) { 
                  return instructors[0];
                }
              }
          }
          
          //If we haven't found any instructors, then just return the shortened name.
          return {shortened_name: name};
        })
      }
    }, 
    final_exam: { type: GraphQLString },
    max_capacity: { type: GraphQLFloat },
    meetings: { type: new GraphQLList(meetingType) },
    num_section_enrolled: { type: GraphQLFloat },
    num_total_enrolled: { type: GraphQLFloat },
    num_new_only_reserved: { type: GraphQLFloat },
    num_on_waitlist: { 
      type: GraphQLFloat, 
      resolve: (offering) => {
          return offering.num_on_waitlist === 'n/a' ? null : offering.num_on_waitlist;
        } 
    },
    num_requested: { type: GraphQLFloat },
    restrictions: { type: GraphQLString },
    section: { type: sectionInfoType },  
    status: { type: GraphQLString },
    units: { type: GraphQLString },
    course: { 
      type: courseType,
      resolve: (offering) => {
        // Get the course from the cache.
        const course = getCourse(offering.course.id);
        // If it's not in our cache, return whatever information was provided.
        // Usually, it will at least have id, department, and number
        return course ? course : offering.course;
      }
    }
  })
});

// Validate Schedule Query Arguments
function validateScheduleArgs(args) {
  // Assert that a term is provided (year and quarter)
  // year and quarter are non-nullable, so they should never be false
  if (!(args.year && args.quarter)) {
    throw new ValidationError("Must provide both a year and a quarter.");
  }
  // Assert that GE, Department, Section Codes, or Instructor is provided
  if (!(args.ge || args.department || args.section_codes || args.instructor)){
    throw new ValidationError("Must specify at least one of the following: ge, department, section_codes, or instructor.")
  }
}

export {meetingType, sectionInfoType, courseOfferingType, validateScheduleArgs};