import { Resolver, Mutation, Query, FieldResolver, Root, Args, Arg, Ctx, Directive } from 'type-graphql'
import { Domain } from '@things-factory/shell'
import { TrackingCamera, TrackingCameraList } from './tracking-camera-type'
import { CameraParameter, Matrix, HandEyeConfig, ROI, Pose } from '../robotarm/robotarm-type'

import { config, logger } from '@things-factory/env'
import { ROBOTICS_OBJECT_TYPES } from '../../controllers/robotics-types'
import { ConnectionManager } from '@things-factory/integration-base'
import { dataBridge } from '../../controllers/data-bridge/data-bridge'
import { DataBridgeConnectionManager } from '../../controllers/data-bridge/data-bridge-manager'

import spawn from 'await-spawn'

// extend type Mutation {
//   detectTrackingCameraROIs(name: String!): [ROI]
//   calibrateHandeyeMatrix(name: String!): Matrix
//   calibrateCameraParameter(name: String!): CameraParameter
//   captureCalibrateResult(name: String!): Boolean
//   getCalibrateResult(name: String!): Boolean
//   finishCameraCalibrate(name: String!): Boolean
//   finishHandeyeCalibrate(name: String!): Boolean
//   startHandeyeCalibrate(name: String!): Boolean
//   getHandeyeResult(name: String!): Boolean
// }

const visionConfig = config.get('vision', {})
const cameraCalibratorProgram = visionConfig.camera?.cameraCalibrator?.program
const ROIDetectorProgram = visionConfig.camera?.ROIDetector?.program
const handEyeCalibratorProgram = visionConfig.camera?.handEyeCalibrator?.program

@Resolver(TrackingCamera)
export class TrackingCameraMutation {
  @Mutation(returns => [ROI], { description: 'To start roi configuration and get the result' })
  async detectTrackingCameraROIs(
    @Arg('name', type => String, { nullable: false }) name: string,
    @Ctx() context: any
  ): Promise<ROI[]> {
    const { domain } = context.state

    var camera = ConnectionManager.getConnectionInstanceByName(domain, name)
    if (!camera || camera.discriminator !== ROBOTICS_OBJECT_TYPES.CAMERA) {
      throw Error(`Tracking Camera '${name}' Not Found`)
    }

    var executable = ROIDetectorProgram[0]
    var params = [...ROIDetectorProgram.slice(1), name]

    var rois = await spawn(executable, params)

    return JSON.parse(rois.toString())
  }

  @Mutation(returns => Matrix, { description: 'To start handeye calibration and get the result' })
  async calibrateHandeyeMatrix(
    @Arg('name', type => String, { nullable: false }) name: string,
    @Ctx() context: any
  ): Promise<Matrix> {
    const { domain } = context.state

    var camera = ConnectionManager.getConnectionInstanceByName(domain, name)
    if (!camera || camera.discriminator !== ROBOTICS_OBJECT_TYPES.CAMERA) {
      throw Error(`Tracking Camera '${name}' Not Found`)
    }

    var executable = handEyeCalibratorProgram[0]
    var params = [...handEyeCalibratorProgram.slice(1), name]

    var calibrationMatrix = await spawn(executable, params)

    logger.info(`handeye matrix:\n'${calibrationMatrix?.toString()}'`)

    return JSON.parse(calibrationMatrix.toString())
  }

  @Mutation(returns => CameraParameter, { description: 'To start handeye calibration and get the result' })
  async calibrateCameraParameter(
    @Arg('name', type => String, { nullable: false }) name: string,
    @Ctx() context: any
  ): Promise<CameraParameter> {
    const { domain } = context.state

    var camera = ConnectionManager.getConnectionInstanceByName(domain, name)
    if (!camera || camera.discriminator !== ROBOTICS_OBJECT_TYPES.CAMERA) {
      throw Error(`Tracking Camera '${name}' Not Found`)
    }

    let activeApp = DataBridgeConnectionManager.getActive()
    if (activeApp === '' || activeApp === name) {
      // TODO: control to run multiple python applications here
      DataBridgeConnectionManager.set(name, 'start')
    }

    var executable = cameraCalibratorProgram[0]
    var params = [...cameraCalibratorProgram.slice(1), name]

    var cameraParameter = await spawn(executable, params)

    logger.info(`calibration matrix:\n'${cameraParameter?.toString()}'`)

    return JSON.parse(cameraParameter.toString())
  }

  @Mutation(returns => Boolean, { description: 'To send camera calibration snapshot to the python module' })
  async captureCalibrateResult(
    @Arg('name', type => String, { nullable: false }) name: string,
    @Ctx() context: any
  ): Promise<boolean> {
    const { domain } = context.state

    var camera = ConnectionManager.getConnectionInstanceByName(domain, name)
    if (!camera || camera.discriminator !== ROBOTICS_OBJECT_TYPES.CAMERA) {
      throw Error(`Tracking Camera '${name}' Not Found`)
    }

    var dataBridgeName = 'cameracalib' + ':' + name
    dataBridge.sendCmdToExt('snapshot', dataBridgeName)

    // TODO: check if this kina return value is available
    return true
  }

  @Mutation(returns => Boolean, { description: 'To send camera calibration snapshot to the python module' })
  async getCalibrateResult(
    @Arg('name', type => String, { nullable: false }) name: string,
    @Ctx() context: any
  ): Promise<boolean> {
    const { domain } = context.state

    var camera = ConnectionManager.getConnectionInstanceByName(domain, name)
    if (!camera || camera.discriminator !== ROBOTICS_OBJECT_TYPES.CAMERA) {
      throw Error(`Tracking Camera '${name}' Not Found`)
    }

    var dataBridgeName = 'cameracalib' + ':' + name
    dataBridge.sendCmdToExt('result', dataBridgeName)

    // TODO: check if this kina return value is available
    return true
  }

  @Mutation(returns => Boolean, { description: 'To finish the current camera calibration with python module' })
  async finishCameraCalibrate(
    @Arg('name', type => String, { nullable: false }) name: string,
    @Ctx() context: any
  ): Promise<boolean> {
    const { domain } = context.state

    var camera = ConnectionManager.getConnectionInstanceByName(domain, name)
    if (!camera || camera.discriminator !== ROBOTICS_OBJECT_TYPES.CAMERA) {
      throw Error(`Tracking Camera '${name}' Not Found`)
    }

    var dataBridgeName = 'cameracalib' + ':' + name
    dataBridge.sendCmdToExt('exit', dataBridgeName)

    // TODO: check if this kina return value is available
    return true
  }

  @Mutation(returns => Boolean, { description: 'To finish the current handeye calibration with python module' })
  async finishHandeyeCalibrate(
    @Arg('name', type => String, { nullable: false }) name: string,
    @Ctx() context: any
  ): Promise<boolean> {
    const { domain } = context.state

    var camera = ConnectionManager.getConnectionInstanceByName(domain, name)
    if (!camera || camera.discriminator !== ROBOTICS_OBJECT_TYPES.CAMERA) {
      throw Error(`Tracking Camera '${name}' Not Found`)
    }

    var dataBridgeName = 'handeyecalib' + ':' + name
    dataBridge.sendCmdToExt('exit', dataBridgeName)

    // TODO: check if this kina return value is available
    return true
  }

  @Mutation(returns => Boolean, { description: 'To start handeye calibration' })
  async startHandeyeCalibrate(
    @Arg('name', type => String, { nullable: false }) name: string,
    @Ctx() context: any
  ): Promise<boolean> {
    const { domain } = context.state

    var camera = ConnectionManager.getConnectionInstanceByName(domain, name)
    if (!camera || camera.discriminator !== ROBOTICS_OBJECT_TYPES.CAMERA) {
      throw Error(`Tracking Camera '${name}' Not Found`)
    }

    var dataBridgeName = 'handeyecalib' + ':' + name
    dataBridge.sendCmdToExt('start', dataBridgeName)

    // TODO: check if this kina return value is available
    return true
  }

  @Mutation(returns => Boolean, { description: 'To get the result of the current handeye calibration' })
  async getHandeyeResult(
    @Arg('name', type => String, { nullable: false }) name: string,
    @Ctx() context: any
  ): Promise<boolean> {
    const { domain } = context.state

    var camera = ConnectionManager.getConnectionInstanceByName(domain, name)
    if (!camera || camera.discriminator !== ROBOTICS_OBJECT_TYPES.CAMERA) {
      throw Error(`Tracking Camera '${name}' Not Found`)
    }

    var dataBridgeName = 'handeyecalib' + ':' + name
    dataBridge.sendCmdToExt('result', dataBridgeName)

    // TODO: check if this kina return value is available
    return true
  }
}
