import camera from '@ohos.multimedia.camera'
import deviceInfo from '@ohos.deviceInfo'
import fileio from '@ohos.fileio'
import image from '@ohos.multimedia.image'
import media from '@ohos.multimedia.media'
import mediaLibrary from '@ohos.multimedia.mediaLibrary'
import Logger from '../utils/Logger'
import MediaUtils from './MediaUtils'
import ThumbnailGetter from './ThumbnailGetter'
import { GlobalContext } from '../utils/GlobalThis'
import { Context } from '@ohos.abilityAccessCtrl'

const CameraSize = {
  WIDTH: 1280,
  HEIGHT: 720
}
const TAG = 'CameraService'

class CameraService {
  private mediaUtil = MediaUtils.getInstance()
  private cameraManager: camera.CameraManager = undefined
  cameras: Array<camera.CameraDevice> = undefined
  private cameraInput: camera.CameraInput = undefined
  private previewOutput: camera.PreviewOutput = undefined
  private photoOutput: camera.PhotoOutput = undefined
  private cameraOutputCapability: camera.CameraOutputCapability = undefined
  private captureSession: camera.CaptureSession = undefined
  private mReceiver: image.ImageReceiver = undefined
  private videoPrepareFile: mediaLibrary.FileAsset = undefined
  private mFileAssetId = 0
  private avRecorder: media.AVRecorder = undefined
  private videoOutput: camera.VideoOutput = undefined
  private mThumbnailGetter = new ThumbnailGetter()
  private mIsStartRecording = false
  private handleTakePicture: (photoUri: string) => void = undefined
  private videoConfig: any = {
    videoSourceType: 1,
    profile: {
      fileFormat: 'mp4',
      videoBitrate: 5000000,
      videoCodec: 'video/avc',
      videoFrameWidth: 640,
      videoFrameHeight: 480,
      videoFrameRate: 30
    },
    url: 'file:///data/media/01.mp4',
    orientationHint: 0,
    maxSize: 100,
    maxDuration: 500,
    rotation: 0
  }
  resolution: any = null
  photoResolution: any = null
  videoResolution: any = null

  constructor() {
  }

  async savePicture(buffer: ArrayBuffer, img: image.Image) {
    try {
      Logger.info(TAG, 'savePicture')
      let imgFileAsset = await this.mediaUtil.createAndGetUri(mediaLibrary.MediaType.IMAGE)
      let imgPhotoUri = imgFileAsset.uri
      Logger.info(TAG, `photoUri = ${imgPhotoUri}`)
      let imgFd = await this.mediaUtil.getFdPath(imgFileAsset)
      Logger.info(TAG, `fd = ${imgFd}`)
      await fileio.write(imgFd, buffer)
      await imgFileAsset.close(imgFd)
      await img.release()
      Logger.info(TAG, 'save image done')
      if (this.handleTakePicture) {
        this.handleTakePicture(imgPhotoUri)
      }
    } catch (err) {
      Logger.error(TAG, `save picture err ${err.message}`)
    }
  }

  public async createVideoFd(): Promise<number> {
    Logger.info(TAG, `getVideoFd E`)
    try {
      let dataUri = await this.mediaUtil.createAndGetUri(mediaLibrary.MediaType.VIDEO)
      this.videoPrepareFile = await this.mediaUtil.queryFile(dataUri);
      const fdNumber = await this.videoPrepareFile.open('Rw')
      return fdNumber;
    } catch (err) {
      Logger.error(TAG, `createVideoFd err: ` + err)
    }
    Logger.info(TAG, `getVideoFd X`)
  }

  async initCamera(surfaceId: number, cameraDeviceIndex: number, obj?, photoIndex?, previewObj?) {
    try {
      if (deviceInfo.deviceType === 'default') {
        this.videoConfig.videoSourceType = 1
      } else {
        this.videoConfig.videoSourceType = 1
      }
      Logger.info(TAG, `cameraDeviceIndex success: ${cameraDeviceIndex}`)
      await this.releaseCamera()
      await this.getCameraManagerFn()
      await this.getSupportedCamerasFn()
      await this.getSupportedOutputCapabilityFn(cameraDeviceIndex)
      if (previewObj) {
        previewObj.format = this.cameraOutputCapability.previewProfiles[0].format
        Logger.info(TAG, `previewObj format: ${previewObj.format}`)
      }
      await this.createPreviewOutputFn(previewObj ? previewObj : this.cameraOutputCapability.previewProfiles[0], surfaceId)
      await this.createPhotoOutputFn(obj ? obj : this.cameraOutputCapability.photoProfiles[photoIndex?photoIndex:0])
      await this.createCameraInputFn(this.cameras[cameraDeviceIndex])
      await this.cameraInputOpenFn()
      await this.sessionFlowFn()

    } catch (err) {
      Logger.error(TAG, 'initCamera err: ' + JSON.stringify(err.message))
    }
  }

  setTakePictureCallback(callback) {
    this.handleTakePicture = callback
  }

  // 拍照
  async takePicture(imageRotation?) {
    try {
      Logger.info(TAG, 'takePicture start')
      let photoSettings = {
        rotation: imageRotation ? Number(imageRotation) : 0,
        quality: 1,
        location: {
          latitude: 0,
          longitude: 0,
          altitude: 0
        },
        mirror: false
      }
      Logger.info(TAG, `photoOutput capture photoSettings: ` + JSON.stringify(photoSettings))
      await this.photoOutput.capture(photoSettings)
      Logger.info(TAG, 'takePicture end')
    } catch (err) {
      Logger.error(TAG, `takePicture fail err: ${JSON.stringify(err)}`)
    }
  }

  public async createVideoOutput() {
    Logger.info(TAG, `createVideoOutput start`)
    this.mFileAssetId = await this.createVideoFd()
    this.videoConfig.url = `fd://${this.mFileAssetId.toString()}`
    await media.createAVRecorder().then((recorder) => {
      Logger.info(TAG, `createVideoOutput createAVRecorder record: ${recorder}`)
      this.avRecorder = recorder
    })
    if (this.avRecorder != null) {
      this.avRecorder.on('error', (error) => {
        if (error) {
          Logger.error(TAG, `createVideoOutput error: ${JSON.stringify(error)}`)
        }
      })
      Logger.info(TAG, `createVideoOutput size = ${JSON.stringify(this.cameraOutputCapability.videoProfiles[0].size)}`)
      this.videoConfig.profile.videoFrameWidth = this.cameraOutputCapability.videoProfiles[0].size.width
      this.videoConfig.profile.videoFrameHeight = this.cameraOutputCapability.videoProfiles[0].size.height
      Logger.info(TAG, `createVideoOutput videoConfig: ` + JSON.stringify(this.videoConfig))
      await this.avRecorder.prepare(this.videoConfig)
      Logger.info(TAG, `createVideoOutput AVRecorder.prepare succeed.`)
    } else {
      Logger.error(TAG, `createVideoOutput createAVRecorder failed.`)
      return
    }

    const videoId = await this.avRecorder.getInputSurface()
    Logger.info(TAG, `createVideoOutput profileVideo =  ${JSON.stringify(this.cameraOutputCapability.videoProfiles[0])}.`)
    try {
      this.videoOutput = this.cameraManager.createVideoOutput(this.cameraOutputCapability.videoProfiles[0], videoId)
    } catch (error) {
      Logger.error(TAG, `createVideoOutput failed: ${JSON.stringify(error)}`)
    }
    Logger.info(TAG, `createVideoOutput end`)
  }

  public async releaseVideoOutput() {
    Logger.info(TAG, `releaseVideoOutput start`)
    if (this.videoOutput) {
      Logger.info(TAG, `LoggerreleaseVideoOutput start`)
      try {
        await this.videoOutput.release()
      } catch (error) {
        Logger.error(TAG, `LoggerreleaseVideoOutput failed: ${JSON.stringify(error)}`)
      }
      Logger.info(TAG, `LoggerreleaseVideoOutput end`)
      this.videoOutput = null
    }
    Logger.info(TAG, `releaseVideoOutput end`)
  }

  // 开始录制
  async StartRecording() {
    try {
      Logger.info(TAG, `StartRecording begin`)
      await this.captureSession.stop()
      this.captureSession.beginConfig()
      this.captureSession.removeOutput(this.photoOutput)
      Logger.info(TAG, `StartRecording removeOutput finished.`)
      if (this.videoOutput) {
        await this.captureSession.removeOutput(this.videoOutput)
        Logger.info(TAG, `old videoOutput has been removed.`)
      }
      await this.createVideoOutput()
      this.captureSession.addOutput(this.videoOutput)
      Logger.info(TAG, `StartRecording addOutput finished.`)
      await this.captureSession.commitConfig()
      Logger.info(TAG, `StartRecording commitConfig finished.`)
      await this.captureSession.start()
      Logger.info(TAG, `StartRecording Session.start finished.`)
    } catch (err) {
      Logger.error(TAG, `remove videoOutput ${JSON.stringify(err)}`)
    }
    await this.videoOutput.start().then(() => {
      Logger.info(TAG, `videoOutput.start()`)
    })
    await this.avRecorder.start().then(() => {
      Logger.info(TAG, `AVRecorder.start()`)
    })
    this.mIsStartRecording = true
    Logger.info(TAG, `StartRecording end`)
  }

  // 停止录制
  async stopRecording() {
    if (!this.videoOutput || !this.avRecorder) {
      Logger.error(TAG, `stopRecording error videoOutPut: ${this.videoOutput},AVRecorder: ${this.avRecorder} .`)
      return
    }
    this.mIsStartRecording = false
    try {
      await this.avRecorder.stop()
      await this.avRecorder.release()
    } catch (err) {
      Logger.error(TAG, `stop AVRecorder ${err}`)
    }

    try {
      await this.videoOutput.stop()
    } catch (err) {
      Logger.error(TAG, `stop videoOutput ${err}`)
    }

    if (this.mFileAssetId != undefined) {
      await this.videoPrepareFile.close(this.mFileAssetId)
      this.mFileAssetId = undefined
      Logger.info(TAG, `fileAsset.close().`)
    }

    const thumbnailPixelMap = await this.mThumbnailGetter.getThumbnailInfo(640, 480)
    Logger.info(TAG, `stopRecording invoke X.`)
    return thumbnailPixelMap
  }

  // 查询相机设备在模式下支持的输出能力
  async getSupportedOutputCapabilityFn(cameraDeviceIndex) {
    Logger.info(TAG, `cameraOutputCapability cameraId: ${this.cameras[cameraDeviceIndex].cameraId}`)
    this.cameraOutputCapability = this.cameraManager.getSupportedOutputCapability(this.cameras[cameraDeviceIndex])
    let previewSize = []
    let photoSize = []
    let videoSize = []
    this.cameraOutputCapability.previewProfiles.forEach((item, index) => {
      Logger.info(TAG, `cameraOutputCapability previewProfiles index: ${index}, item:` + JSON.stringify(item))
      previewSize.push({
        value: `${item.size.width}x${item.size.height}`
      })
    })
    this.cameraOutputCapability.photoProfiles.forEach((item, index) => {
      Logger.info(TAG, `cameraOutputCapability photoProfiles index: ${index}, item:` + JSON.stringify(item))
      photoSize.push({
        value: `${item.size.width}x${item.size.height}`
      })
    })
    this.cameraOutputCapability.videoProfiles.forEach((item, index) => {
      Logger.info(TAG, `cameraOutputCapability videoProfiles index: ${index}, item:` + JSON.stringify(item))
      videoSize.push({
        value: `${item.size.width}x${item.size.height}`
      })
    })
    Logger.info(TAG, `cameraOutputCapability previewProfiles:` + JSON.stringify(this.cameraOutputCapability.previewProfiles))
    Logger.info(TAG, `cameraOutputCapability photoProfiles:` + JSON.stringify(this.cameraOutputCapability.photoProfiles))
    Logger.info(TAG, `cameraOutputCapability videoProfiles:` + JSON.stringify(this.cameraOutputCapability.videoProfiles))
    Logger.info(TAG, `cameraOutputCapability previewProfiles previewSize:` + JSON.stringify(previewSize))
    this.resolution = previewSize
    this.photoResolution = photoSize
    this.videoResolution = videoSize
    return previewSize
  }

  public async releaseRecording() {
    Logger.info(TAG, `releaseRecording start`)
    if (!this.avRecorder) {
      Logger.info(TAG, `AVRecorder has not been created.`)
      return
    }
    if (this.mIsStartRecording) {
      await this.stopRecording()
    }
    await this.avRecorder.release().then(() => {
      Logger.info(TAG, `AVRecorder.release() success.`)
      this.avRecorder = undefined
    })
    Logger.info(TAG, `releaseRecording end`)
  }

  // 释放会话及其相关参数
  async releaseCamera() {
    Logger.info(TAG, `releaseCamera start`)
    await this.releaseRecording()
    await this.releaseVideoOutput()
    await this.releasePhotoOutput()
    await this.releaseSession()
    Logger.info(TAG, `releaseCamera end`)
  }

  // 释放会话
  async releaseSession() {
    Logger.info(TAG, `releaseSession start`)
    if (this.captureSession) {
      try {
        await this.captureSession.stop()
        await this.captureSession.release()
        this.captureSession = null
      } catch (error) {
        Logger.error(TAG, `releaseSession failed: ${JSON.stringify(error)}`)
      }
    }
    Logger.info(TAG, `releaseSession end`)
  }

  // 获取相机管理器实例
  async getCameraManagerFn() {
    try {
      this.cameraManager = camera.getCameraManager(GlobalContext.getContext().getObject("context") as Context)
      Logger.info(TAG, `getCameraManager success: ` + JSON.stringify(this.cameraManager))
    } catch (err) {
      Logger.error(TAG, `getCameraManagerFn fail err: ${err}, message: ${err.message}, code: ${err.code}`)
    }
  }

  // 获取支持指定的相机设备对象
  async getSupportedCamerasFn() {
    try {
      this.cameras = this.cameraManager.getSupportedCameras()
      if (this.cameras) {
        Logger.info(TAG, `getCameras success.`)
        for (let i = 0; i < this.cameras.length; i++) {
          Logger.info(TAG, `--------------Camera Info-------------`)
          Logger.info(TAG, `camera_id: ${this.cameras[i].cameraId}`)
          Logger.info(TAG, `cameraPosition: ${this.cameras[i].cameraPosition}`)
          Logger.info(TAG, `cameraType: ${this.cameras[i].cameraType}`)
          Logger.info(TAG, `connectionType: ${this.cameras[i].connectionType}`)
        }
      }
    } catch (err) {
      Logger.error(TAG, `getSupportedCamerasFn fail err: ${err}, message: ${err.message}, code: ${err.code}`)
    }
  }

  // 创建previewOutput输出对象
  async createPreviewOutputFn(previewProfilesObj, surfaceId) {
    try {
      Logger.info(TAG, `createPreviewOutputFn previewProfilesObj success: ` + JSON.stringify(previewProfilesObj))
      this.previewOutput = this.cameraManager.createPreviewOutput(previewProfilesObj, surfaceId.toString())
      Logger.info(TAG, `createPreviewOutputFn success: ` + JSON.stringify(this.previewOutput))
    } catch (err) {
      Logger.error(TAG, `createPreviewOutputFn fail err: ${err}, message: ${err.message}, code: ${err.code}`)
    }
  }

  // 创建photoOutput输出对象
  async createPhotoOutputFn(photoProfileObj) {
    try {
      Logger.info(TAG, `createPhotoOutputFn photoProfileObj success: ` + JSON.stringify(photoProfileObj))
      try {
        this.mReceiver = image.createImageReceiver(CameraSize.WIDTH, CameraSize.HEIGHT, image.ImageFormat.JPEG, 8)
        Logger.info(TAG, 'createImageReceiver')
        this.mReceiver.on('imageArrival', () => {
          Logger.info(TAG, 'imageArrival')
          this.mReceiver.readNextImage((err, image) => {
            Logger.info(TAG, 'readNextImage')
            if (err || image === undefined) {
              Logger.error(TAG, 'failed to get valid image')
              return
            }
            image.getComponent(4, (errMsg, img) => {
              Logger.info(TAG, 'getComponent')
              if (errMsg || img === undefined) {
                Logger.info(TAG, 'failed to get valid buffer')
                return
              }
              let buffer
              if (img.byteBuffer) {
                buffer = img.byteBuffer
              } else {
                Logger.error(TAG, 'img.byteBuffer is undefined')
              }
              this.savePicture(buffer, image)
            })
          })
        })
      } catch (err) {
        Logger.error(TAG, `image Receiver err ${err.message}`)
      }
      let mSurfaceId = await this.mReceiver.getReceivingSurfaceId()
      this.photoOutput = this.cameraManager.createPhotoOutput(photoProfileObj, mSurfaceId)
      Logger.info(TAG, `createPhotoOutputFn success: ` + JSON.stringify(this.photoOutput))
    } catch (err) {
      Logger.error(TAG, `createPhotoOutputFn fail err: ${err}, message: ${err.message}, code: ${err.code}`)
    }
  }

  public async releasePhotoOutput() {
    Logger.info(TAG, `releasePhotoOutput start`)
    if (this.photoOutput) {
      try {
        await this.photoOutput.release()
        this.photoOutput = null
      } catch (error) {
        Logger.error(TAG, `releasePhotoOutput failed: ${JSON.stringify(error)}`)
      }
    }
    if (this.mReceiver) {
      await this.mReceiver.release()
      this.mReceiver = null
    }
    Logger.info(TAG, `releasePhotoOutput end`)
  }

  // 创建cameraInput输出对象
  async createCameraInputFn(cameraDeviceIndex) {
    try {
      this.cameraInput = this.cameraManager.createCameraInput(cameraDeviceIndex)
      Logger.info(TAG, `createCameraInputFn success: ${this.cameraInput}`)
    } catch (err) {
      Logger.error(TAG, `createCameraInputFn fail err: ${err}, message: ${err.message}, code: ${err.code}`)
    }
  }

  // 打开相机
  async cameraInputOpenFn() {
    await this.cameraInput.open()
      .then((data) => {
        Logger.info(TAG, `cameraInputOpenFn open success: ${data}`)
      })
      .catch((err) => {
        Logger.error(TAG, `cameraInputOpenFn fail err: ${err}, message: ${err.message}, code: ${err.code}`)
      })
  }

  // 会话流程
  async sessionFlowFn() {
    // 创建captureSession实例
    this.captureSession = this.cameraManager.createCaptureSession()
    Logger.info(TAG, `createSession captureSession: ${this.captureSession}, cameraInput: ${this.cameraInput}, videoOutPut: ${this.videoOutput}, photoOutPut: ${this.photoOutput},  mPreviewOutput: ${this.previewOutput}`)
    // 开始配置会话
    Logger.info(TAG, `createSession beginConfig.`)
    try {
      this.captureSession.beginConfig()
      await new Promise((resolve) => setTimeout(resolve, 1));
      // cameraInput加入会话
      Logger.info(TAG, `createSession addInput.`)
      this.captureSession.addInput(this.cameraInput)
      // photoOutput加入会话
      Logger.info(TAG, `createSession photo addOutput.`)
      this.captureSession.addOutput(this.photoOutput)
      // previewOutput加入会话
      Logger.info(TAG, `createSession preview addOutput.`)
      this.captureSession.addOutput(this.previewOutput)
    } catch (err) {
      Logger.error(TAG, `Error in camera operation: ${JSON.stringify(err)}`)
    }
    try {
      // 提交配置会话
      await this.captureSession.commitConfig()
      // 开启会话
      await this.captureSession.start()
    } catch (err) {
      Logger.error(TAG, `Failed to open camera: ${JSON.stringify(err)}`)
    }
    Logger.info(TAG, `sessionFlowFn end`)
  }
}

export default new CameraService()