/*
 * Copyright (C) 2020  Prasoon Joshi
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package graphql

import sangria.macros.derive._
import sangria.schema.{Argument, IDType}
import sangria.marshalling.playJson._
import play.api.libs.json._
import models.NiosxUser
import sangria.schema.EnumType
import sangria.schema.EnumValue
import repository.{NiosxAdminRole, NiosxPartnerRole, NiosxPatronRole}
import models.{
  Subject,
  Person,
  Place,
  Partner,
  MediaType,
  DateRange,
  Language
}
import sangria.schema.InputObjectType
import java.time.LocalDate
import sangria.schema.InputField

object SchemaInputObjects {

  val IdArg = Argument("id", IDType, description = "Id of the object.")

  case class DocumentInput(id: String)
  implicit val documentInputFormat = Json.format[DocumentInput]

  implicit val DocumentInputType = deriveInputObjectType[DocumentInput](
    InputObjectTypeName("DocumentInput")
  )
  val documentInputArg = Argument("docuIngest", DocumentInputType)

  case class MappingPair(field: String, columnName: String)
  implicit val mappingPairFormat = Json.format[MappingPair]
  implicit val MappingPairType = deriveInputObjectType[MappingPair](
    InputObjectTypeName("MappingPair")
  )
 
  object ValidField extends Enumeration {
    type AllowedField = Value
    val ME_recordId,
    ME_agencyCode,
    ME_unitId,
    ME_title,
    ME_creator,
    ME_dateOfCreation,
    ME_extent,
    ME_level,
    ME_image,
    ME_subject,
    ME_description,
    ME_location,
    ME_accessRestrict,
    ME_useRestrict,
    ME_language, //TODO: Change to Language type
    ME_unitType,
    ME_format = Value
  }
  
  implicit val afFormat = Json.formatEnum(ValidField)
  implicit val AllowedFieldType = deriveEnumType[ValidField.AllowedField]()

  type FlatRecord = Map[String, String]
  case class FlatRecordMapper(
    field: ValidField.AllowedField,//ValidField.Value,
    columnName: String,
    default: String)
  implicit val flatRecFormat = Json.format[FlatRecordMapper]
  implicit val FlatRecordMapperType = deriveInputObjectType[FlatRecordMapper](
    InputObjectTypeName("FlatRecordMapper"),
    InputObjectTypeDescription("""Map from a flat-record to any level of 
      nested record structure. Ensure column names are unique.""")
  )
  type Crosswalk = List[FlatRecordMapper]

  case class IngestWithCrosswalk(url: String, crosswalk: Crosswalk)
  implicit val ingestWithCrosswalkFormat = Json.format[IngestWithCrosswalk]
  implicit val IngestWithCrosswalkType = deriveInputObjectType[IngestWithCrosswalk](
    InputObjectTypeName("IngestWithCrosswalk")
  )
  val ingestWithCrosswalkInputArg = Argument("ingestWithCrosswalk",
    IngestWithCrosswalkType)

  import models.SangriaMiter._
  implicit val typFormat = Json.formatEnum(models.EntityType)

  implicit val partnerInputFormat = Json.format[Partner]

  case class CsvIngestInput(
    filePath: String,
    crosswalk: Crosswalk,
    partner: Partner)
  implicit val PartnerInputType = deriveInputObjectType[Partner](
    InputObjectTypeName("PartnerInput"))
//    ExcludeInputFields("typ"))
  implicit val csvIngestInputFormat = Json.format[CsvIngestInput]
  implicit val CsvIngestInputType = deriveInputObjectType[CsvIngestInput](
    InputObjectTypeName("CsvIngestInput"))
  val CsvIngestInputArg = Argument("csvIngestInput", CsvIngestInputType)

  case class MilliEntityInput(agencyCode: String,
    recordId: String,
    unitId: String,
    title: String,
    creator: String,
    dateOfCreation: String,
    extent: String,
    level: String,
    partner: String,
    description: Option[String],
    location: Option[String],
    accessRestrict: Option[String],
    useRestrict: Option[String],
    language: Option[String],
    unitType: Option[String],
    format: Option[String])
  implicit val MilliEntityInputFormat = Json.format[MilliEntityInput]
  implicit val MilliEntityInputType = deriveInputObjectType[MilliEntityInput](
    InputObjectTypeName("NiosxEntityInput"))
  val MilliEntityInputArg = Argument("niosxEntityInput",
    MilliEntityInputType)
  
  case class DateRangeInput(from: String, to: String) 
  implicit val DateRangeInputType = deriveInputObjectType[DateRangeInput](
    InputObjectTypeName("DateRangeInput"),
    InputObjectTypeDescription("ISO-8601 formatted string of format: 2017-06-25")
  )

  type GraphId = String

  case class EntityFilterInput(
    blob: String,
    dateRange: Option[DateRangeInput],
    lang: Option[Seq[GraphId]],
    subjects: Option[Seq[GraphId]],
    people: Option[Seq[GraphId]],
    places: Option[Seq[GraphId]],
    partners: Option[Seq[GraphId]])

  case class EntityFilterInputAlt(
    blob: String,
    dateRange: Option[DateRangeInput],
    lang: Option[Seq[Language]],
    subjects: Option[Seq[Subject]],
    people: Option[Seq[Person]],
    places: Option[Seq[Place]],
    partners: Option[Seq[Partner]])//,
//    mediaTypes: Option[Seq[MediaType.Value]])
  implicit val SubjectFormat = Json.format[Subject]
  implicit val PersonFormat = Json.format[Person]
  implicit val PlaceFormat = Json.format[Place]
  implicit val DateRangeFormat = Json.format[DateRangeInput]
  implicit val LanguageFormat = Json.format[Language]
  implicit val MediaTypeFormat = Json.formatEnum(MediaType)
  implicit val EntityFilterAltFormat = Json.format[EntityFilterInputAlt]

  implicit val LanguageInput = deriveInputObjectType[Language](
    InputObjectTypeName("LanguageInput"),
    ExcludeInputFields("typ"))
  implicit val SubjectInput = deriveInputObjectType[Subject](
    InputObjectTypeName("SubjectInput"),
    ExcludeInputFields("typ"))
  implicit val PersonInput = deriveInputObjectType[Person](
    InputObjectTypeName("PersonInput"),
    ExcludeInputFields("typ"))
  implicit val PlaceInput = deriveInputObjectType[Place](
    InputObjectTypeName("PlaceInput"),
    ExcludeInputFields("typ"))
  implicit val MediaTypeInput = deriveEnumType[MediaType.Value]()

  implicit val EntityFilterType = deriveInputObjectType[EntityFilterInput](
    InputObjectTypeName("EntityFilterInput"))
  implicit val EntityFilterFormat = Json.format[EntityFilterInput]
  val EntityFilterInputArg = Argument("entityFilterInputArg", EntityFilterType)

  //User 
  //TODO: Change {UserInput} to something better. Use a Agent type
  //and derive different types of agents that would interact with the 
  //system. There should not be any "users" of the application!

  //TODO: Enum serializer.
//  import repository.UserRole
//  implicit val UserRoleType = EnumType[UserRole]("UserRole",
//    Some("Patron's role"),
//    List(
//      EnumValue("Admin", value = NiosxAdmin),
//      EnumValue("Partner", value = NiosxPartner),
//      EnumValue("Patron", value = NiosxPatron)))
//  implicit val UserRoleFormat = Json.format[UserRole]
  case class UserInput(username: String,
    password: String,
    role: String)
  implicit val UserInputFormat = Json.format[UserInput]
  implicit val UserInputType = deriveInputObjectType[UserInput](
    InputObjectTypeName("NiosxUser"))
  val UserInputArg = Argument("niosxUserInput",
    UserInputType)
  

//  implicit val UserInputType = deriveInputObjectType[NiosxUser](
//    InputObjectTypeName("patron"),
//    InputObjectTypeDescription("niosx patrons"),
//    DocumentInputField("permissions",
//      "Each user role is associated with a list of permissions"),
//    ExcludeInputFields("passHash"))

  //Annotations
  case class BodyInput(typ: String,
    motivation: String,
    value: String,
    format: String,
    language: String,
    creator: String,
    concept: Option[String] = None)
  implicit val BodyInputFormat = Json.format[BodyInput]
  implicit val BodyInputType = deriveInputObjectType[BodyInput](
    InputObjectTypeName("BodyInput")
  )

  case class TargetInput(source: String, targetId: String)
  implicit val TargetInputFormat = Json.format[TargetInput]
  implicit val TargetInputType = deriveInputObjectType[TargetInput](
    InputObjectTypeName("Target")
  )

  //TODO: Move concept from AnnotationInput to BodyInput
  case class AnnotationInput(targetId: String,
    motivation: String,
    body: BodyInput,
    target: TargetInput,
    concept: Option[String])
  implicit val AnnotationInputFormat = Json.format[AnnotationInput]
  implicit val AnnotationInputType = deriveInputObjectType[AnnotationInput](
    InputObjectTypeName("AnnotationInput")
  )

  val AnnotationInputArg = Argument("annotation",
    AnnotationInputType)

  case class SubjectAnnoInput(targetId: String,
    subjectId: Option[String],
    prefLabel: Option[String])
  implicit val SubjectAnnoInFormat = Json.format[SubjectAnnoInput]
  implicit val SubAnnoInType = deriveInputObjectType[SubjectAnnoInput](
    InputObjectTypeName("SubjectAnnoInput")
  )
  val SubjectAnnoInArg = Argument("subjectAnno", SubAnnoInType)

  case class CommentInput(targetId: String,
    value: String)
  implicit val CommentInputFormat = Json.format[CommentInput]
  implicit val CommentInputType = deriveInputObjectType[CommentInput](
    InputObjectTypeName("Comment")
  )
  val CommentInputArg = Argument("comment", CommentInputType)

  case class SubjectIn(prefLabel: String, inScheme: String, id: String)
  implicit val SubjectInFormat = Json.format[SubjectIn]
  implicit val SubjectInType = deriveInputObjectType[SubjectIn](
    InputObjectTypeName("SubjectIn")
  )
  val SubjectInArg = Argument("SubjectIn", SubjectInType)
}
