/*
 * 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.schema._

import SchemaInputObjects._


object SchemaDefinition {

  import sangria.macros.derive._
  
  val Id = Argument("id", IDType, description = "Id of the object.")
  val IdArg = Argument("id", StringType)
  val OffsetArg = Argument("offset", OptionInputType(IntType), 0)
  val LimitArg = Argument("limit", OptionInputType(IntType), 100)
  val NextArg = Argument("next", IntType)
  val CursorArg = Argument("cursor", OptionInputType(StringType))
  val BlobArg = Argument("blob", StringType)

  //Auth related args
  val UsernameArg = Argument("username", StringType)
  val PasswordArg = Argument("password", StringType)

  import models._

  lazy implicit val ETypeEnum = deriveEnumType[EntityType.Value]()

  lazy implicit val UserType = 
    ObjectType("User", fields[SecureGraphqlContext, NiosxUser](
      Field("username", StringType, resolve = _.value.username),
      Field("permissions", OptionType(ListType(StringType)),
        resolve = ctx => ctx.ctx.authorized("VIEW_ALL"){_ =>
          ctx.value.permissions
        })
      ))

  lazy val AuthPayloadType = deriveObjectType[Unit, AuthPayload](
    ObjectTypeName("AuthPayload"))

/*
  implicit lazy val AudienceType = deriveEnumType[Audience.Value]()

  implicit lazy val AmCommonAttrbsType = deriveObjectType[Unit, AmCommonAttrbs](
    ObjectTypeName("am_common")
  )

  implicit val LangEncodingType = deriveEnumType[LangEncoding.Value]()
  implicit val ScriptEncodingType = deriveEnumType[ScriptEncoding.Value]()
  implicit val DateEncodingType = deriveEnumType[DateEncoding.Value]()
  implicit val CountryEncodingType = deriveEnumType[CountryEncoding.Value]()
  implicit val RepositoryEncodingType = deriveEnumType[RepositoryEncoding.Value]()
  implicit val RecordIdType = deriveObjectType[Unit, RecordId](
    ObjectTypeName("recordid")
  )
  implicit val OtherRecordIdType = deriveObjectType[Unit, OtherRecordId](
    ObjectTypeName("otherRecordId")
  )
  implicit val LinkShowEnumType = deriveEnumType[LinkShow.Value]()
  implicit val ActuateEnumType = deriveEnumType[Actuate.Value]()
  implicit val SimpleLinkType = deriveObjectType[Unit, SimpleLink](
    ObjectTypeName("simpleLink")
  )
  implicit val RepresentationType = deriveObjectType[Unit, Representation](
    ObjectTypeName("representation")
  )
  implicit val ControlAttrbsType = deriveObjectType[Unit, ControlAttrbs](
    ObjectTypeName("control_attributes"),
    ExcludeFields("vertex")
  )

  implicit val MaintenanceAgencyAttrbsType = deriveObjectType[Unit,
  MaintenanceAgencyAttrbs](
    ObjectTypeName("maintenanceAgency_attributes")
  )
  implicit val AgencyCodeAttrbsType = deriveObjectType[Unit,
  AgencyCodeAttrbs](
    ObjectTypeName("agencyCode_attributes"),
    ExcludeFields("vertex")
  )
  implicit val AgencyCodeType = deriveObjectType[Unit, AgencyCode](
    ObjectTypeName("agencyCode")
  )
  implicit val MaintenanceAgencyType = deriveObjectType[Unit, MaintenanceAgency](
    ObjectTypeName("maintenanceAgency")
  )
  implicit val ControlType = deriveObjectType[Unit, Control](
    ObjectTypeName("control")
  )

  implicit val EadAttrbsType = deriveObjectType[Unit, EadAttributes](
    ObjectTypeName("ead_attributes"),
    ExcludeFields("vertex")
  )
  implicit val ArchDescAttrbsType = deriveObjectType[Unit, ArchDescAttrbs](
    ObjectTypeName("archDesc_attributes")
  )
  implicit val DidAttrbsType = deriveObjectType[Unit, DidAttrbs](
    ObjectTypeName("did_attributes")
  )
  implicit val UnitIdAttrbsType = deriveObjectType[Unit, UnitIdAttrbs](
    ObjectTypeName("unitId_attributes")
  )
  implicit val UnitIdType = deriveObjectType[Unit, UnitId](
    ObjectTypeName("unitId")
  )
  implicit val UnitTitleType = deriveObjectType[Unit, UnitTitle](
    ObjectTypeName("unitTitle")
  )
  implicit val DidOptionsType: UnionType[Unit] = UnionType(
    "didOptions",
    Some("A union of elements available under Did"),
    types = List(UnitIdType, UnitTitleType)
  )
  //implicit val DidOptsEnum: EnumType[DidOptions] = 
  //  deriveEnumType[DidOptions](
  //    EnumTypeName("didOpts"),
  //    EnumTypeDescription("Union of Did sub-elements")
  //  )
  //implicit val DidType = deriveObjectType[Unit, Did](
  //  ObjectTypeName("did")
  //)
  implicit val DscType = deriveObjectType[Unit, Dsc](
    ObjectTypeName("dsc")
  )
  implicit val ArchDescOptionType: UnionType[Unit] = UnionType(
    "archDescOptions",
    Some("A union of elements available under Archival Description"),
    types = List(DscType)
  )
  implicit val ArchDescType = deriveObjectType[Unit, ArchDesc](
    ObjectTypeName("archdesc")
  )
  implicit val EadType = deriveObjectType[Unit, Ead](
    ObjectTypeName("ead")
  )
*/

  implicit val CreatorType = deriveObjectType[Unit, models.AnnoCreator](
    ObjectTypeName("Creator")
  )
  implicit val GeneratorType = deriveObjectType[Unit, models.AnnoGenerator](
    ObjectTypeName("Generator")
  )
  implicit lazy val NiosxConceptType = ObjectType[Unit, NiosxConcept](
    "NiosxConcept",
    "The concept layer connecting all entities via annotations.",
    () => fields[Unit, NiosxConcept](
      Field("name", StringType,
        Some("""Name of the concept. 
          This is one of system defined values or a user given name."""),
        resolve = _.value.name))
  )
  implicit lazy val NiosxEntityType = InterfaceType[Unit, NiosxEntity](
    "NiosxEntity",
    "All Types identified by the niosx ecosystem",
    () => fields[Unit, NiosxEntity](
      Field("graphId", StringType,
        Some("A unique identity for the entity in the niosx ecosystem."),
        resolve = _.value.graphId),
      Field("type", ETypeEnum,
        Some("The `type` is a String representation of the entity type."),
        resolve = _.value.typ)
      )
  )
  implicit lazy val NiosxEntityUnion: UnionType[Unit] =
   UnionType(
     "NiosxEntityUnion",
     Some("Is one the the niosx Entities."),
     types = List(TextualBodyType, LocResBodyType, SubjectType))

  implicit val IBodyType = InterfaceType[Unit, models.BodyType](
      "body",
      "TextualBody, SpecificResource, etc.",
      () => fields[Unit, models.BodyType](
        Field("typ", ETypeEnum,
          Some("Enum with values TextualBody, SpecificResource, etc."),
          resolve = _.value.typ)
        )
  )
  implicit val TextualBodyType = deriveObjectType[Unit, models.TextualBody](
    ObjectTypeName("TextualBody"),
    Interfaces[Unit, TextualBody](NiosxEntityType)
  )
  implicit lazy val LocResBodyType = ObjectType[Unit, LocalResource](
    "LocalResource",
    "LocalResource wraps a NiosxEntity as the body of the annotation.",
    () => fields[Unit, LocalResource](
      Field("entity", OptionType(NiosxEntityUnion), 
        Some("One of NiosxEntity types"),
        resolve = _.value.entity),
      Field("iri", StringType, 
        Some("IRI of the LocalResource"),
        resolve = _.value.iri),
      Field("graphId", StringType, 
        Some("GraphId of the LocalResource"),
        resolve = _.value.graphId),
      Field("source", StringType, 
        Some("IRI of the Body"),
        resolve = _.value.source),
      Field("concept", NiosxConceptType, 
        Some("Concept with which the Body is attached to the Entity."),
        resolve = _.value.concept),
      Field("type", ETypeEnum, 
        Some("Type of the Body entity."),
        resolve = _.value.typ)
  ))

  implicit val BodyTypeUnion: UnionType[Unit] =
   UnionType(
     "bodyType",
     Some("Can be one of TextualBodyType, etc."),
     types = List(TextualBodyType, LocResBodyType))

  implicit val SpecificResourceType = 
    deriveObjectType[Unit, models.SpecificResource](
      ObjectTypeName("SpecificResourceType")
    )
  implicit val TargetTypeUnion: UnionType[Unit] = 
   UnionType(
     "targetType",
     Some("Can be one of SpecificResource, etc."),
     types = List(SpecificResourceType))


  implicit val AnnotationType = ObjectType[Unit, models.Annotation](
    "Annotation",
    "Annotations object",
    () => fields[Unit, models.Annotation](
      Field("graphId", OptionType(StringType),
        Some("GraphID for annotation node"),
        resolve = _.value.graphId),
      Field("context", StringType,
        resolve = _.value.context),
      Field("id", StringType, Some("id"), resolve = _.value.identity),
      Field("type", ETypeEnum, Some("object type"), resolve = _.value.typ),
      Field("motivation", StringType,
        Some("Annotation motivation"), resolve = _.value.motivation),
      Field("creator", CreatorType,
        Some("The agent responsible for creation of annotation"),
        resolve = _.value.creator),
      Field("created", StringType,
        Some("The date-time of creation of annotation"),
        resolve = _.value.created),
      Field("generator", GeneratorType,
        Some("The agent responsible for generation of annotation"),
        resolve = _.value.generator),
      Field("generated", StringType,
        Some("Date-time of generation of annotation"),
        resolve = _.value.generated),
      Field("body", OptionType(ListType(BodyTypeUnion)),
        Some("Annotation body"),
        resolve = _.value.body),
      Field("target", ListType(TargetTypeUnion), Some("Annotation target"),
        resolve = _.value.target)
      ))

  implicit val SubjectType = deriveObjectType[Unit, Subject](
    ObjectTypeName("Subject"))

  implicit val ImageSizeType = deriveEnumType[ImageSize.Value]()
  
  implicit val ImageType = deriveObjectType[Unit, Image](
    ObjectTypeName("Image"))

  implicit val MediaTypeType = deriveEnumType[MediaType.Value]()
  
  implicit val PartnerType = deriveObjectType[Unit, Partner](
    ObjectTypeName("Partner"))

  implicit lazy val MilliEntityType = deriveObjectType[Unit, MilliEntity](
    ObjectTypeName("MilliEntity")
  )
  
  implicit val PageEdgeType = deriveObjectType[Unit, PageEdge](
    ObjectTypeName("PageEdge"))

  implicit val LanguageType = deriveObjectType[Unit, Language](
    ObjectTypeName("Language"))

  implicit val DateRangeType = ObjectType[Unit, DateRange](
    "DateRange",
    "Date range, inclusive",
    () => fields[Unit, DateRange](
      Field("from", StringType,
        Some("Date starting from"),
        resolve = _.value.from.toString()),
      Field("to", StringType,
        Some("Date ending"),
        resolve = _.value.to.toString)
      ))

  implicit val PersonType = deriveObjectType[Unit, Person](
    ObjectTypeName("Person"))
//    AddFields(
//      Field("isSelected", BooleanType, resolve = _ => true)))

  implicit val PlaceType = deriveObjectType[Unit, Place](
    ObjectTypeName("Place"))

//  implicit val MediaTypeType = deriveEnumType[MediaType.Value]()

  implicit val EntityFilterType = deriveObjectType[Unit, EntityFilter](
    ObjectTypeName("EntityFilter"))

  implicit val PageInfoType = deriveObjectType[Unit, PageInfo](
    ObjectTypeName("PageInfo"))

  implicit val PageType = deriveObjectType[Unit, Page](
    ObjectTypeName("Page"))
  //************ TinkerPop Objects exposed via the GraphQL api ************//

  implicit val AmCommonAttrbsVertexType = 
    deriveObjectType[Unit,AmCommonAttrbsVertex](
      ObjectTypeName("Attrbs")
    )

  implicit val RecordIdVertex = deriveObjectType[Unit, RecordIdVertex](
    ObjectTypeName("recordId_vertex")
  )
  implicit val ControlVertexType = deriveObjectType[Unit, ControlVertex](
    ObjectTypeName("control_vertex")
  )
  implicit val EadVertexType = deriveObjectType[Unit, EadVertex](
    ObjectTypeName("ead_vertex")
  )


  val QueryType = ObjectType(
    "query",
    () => fields[SecureGraphqlContext, Unit](
      Field("searchCollections",
        OptionType(PageType),
        description = Some("Paginated list of entities"),
        arguments =  
          NextArg :: CursorArg :: EntityFilterInputArg :: Nil,
        resolve = c => 
          c.ctx.dataRepository.paginatedSearch(
            c arg NextArg,
            c arg CursorArg,
            c arg EntityFilterInputArg)),
      Field("crudeSearch",
        ListType(MilliEntityType),
        description = Some("Returns everything! Be careful!"),
        arguments = BlobArg :: Nil,
        resolve = c => c.ctx.dataRepository.nonPaginatedSearch(c arg BlobArg)),
      Field("isValidGraphId",
        BooleanType,
        description = Some("Test if there is an object with this graphId"),
        arguments = IdArg :: Nil,
        resolve = c => c.ctx.dataRepository.isValidGraphId(c arg IdArg)),
      Field("findEntity",
        OptionType(MilliEntityType),
        description = Some("Look up an entity with its graphId"),
        arguments = IdArg :: Nil,
        resolve = c => c.ctx.dataRepository.findEntity(c arg IdArg)),
      Field("findUser",
        OptionType(UserType),
        description = Some("Look up a user with its username"),
        arguments = UsernameArg :: Nil,
        resolve = c => c.ctx.authorized("VIEW_USER")(user => 
            c.ctx.userRepository.findUserByUsername(c arg UsernameArg)))
    )
  )

  val XmlArg = Argument("xml", StringType)
  val SchemaArg = Argument("schema", StringType)
  val RDFFileArg = Argument("rdf_file", StringType)

  val MutationType = ObjectType(
    "mutation",
    fields[SecureGraphqlContext, Unit](
      Field("rdfIngest",
        IntType,
        arguments = RDFFileArg :: SchemaArg :: Nil,
        resolve = c =>
          c.ctx.dataRepository
            .rdfIngest(c arg SchemaArg, c arg RDFFileArg)),
      Field("ingestWithCrosswalk",
        IntType,
        arguments = CsvIngestInputArg :: Nil,
        resolve = c => c.ctx.authorized("BULK_UPLOAD"){_ =>
          c.ctx.dataRepository.csvIngest(c arg CsvIngestInputArg)}),
      Field("addEntity",
        OptionType(MilliEntityType),
        arguments = MilliEntityInputArg :: Nil,
        resolve = c => c.ctx.authorized("EDIT_ENTITY"){ _ =>
          c.ctx.dataRepository.addEntity(c arg MilliEntityInputArg)}),
      Field("addAnnotation",
        OptionType(AnnotationType),
        arguments = AnnotationInputArg :: Nil,
        resolve = c => c.ctx.authorized("EDIT_ANNO"){ _ =>
          c.ctx.dataRepository.addAnnotation(c arg AnnotationInputArg)}),
      Field("addSubAnno",
        OptionType(AnnotationType),
        arguments = SubjectAnnoInArg :: Nil,
        resolve = c => c.ctx.authorized("EDIT_ANNO"){ _ =>
          c.ctx.dataRepository.addSubjectAnno(c arg SubjectAnnoInArg)}),
      Field("addComment",
        OptionType(AnnotationType),
        arguments = CommentInputArg :: Nil,
        resolve = c => c.ctx.authorized("EDIT_ANNO") { _ =>
          c.ctx.dataRepository.addComment(c arg CommentInputArg)}),
      Field("addSubject",
        OptionType(SubjectType),
        arguments = SubjectInArg :: Nil,
        resolve = c => c.ctx.authorized("EDIT_ANNO") { _ =>
          c.ctx.dataRepository.addSubject(c arg SubjectInArg)}),
      Field("login", OptionType(AuthPayloadType),
        arguments = UsernameArg :: PasswordArg :: Nil,
        resolve = c => 
          UpdateCtx(
            c.ctx.login(c arg UsernameArg, c arg PasswordArg)){
              case Some(authPayload) =>
                c.ctx.copy(token = Some(authPayload.token))
              case None => c.ctx
            }),
      Field("register", 
        StringType,
        arguments = UserInputArg :: Nil,
        resolve = c => 
          c.ctx.userRepository.addUser(c arg UserInputArg))
    )
  )

 
  val schema = Schema(QueryType, Some(MutationType))

}
