import moment from 'moment'
import { formatDate, getAverageValue, getAppVersion } from '../util'
import _ from 'lodash'
import {getFeatures} from './map_util'

export default async function generateReport (params) {
  let {projectInfo, taskInfo, reportInfo, photosString, tilejson} = params
  let photos = JSON.parse(photosString)
  let report = {
    title: '成果质量报告',
    projectName: projectInfo.name,
    taskName: taskInfo.name,
    project_id: taskInfo.project_id,
    task_id: taskInfo.task_id,
    user_id: taskInfo.user_id,
    data_type: taskInfo.data_type,
    sections: [
      {
        title: '任务概览',
        content: [
          {
            type: 'form',
            items: getTaskPreview(taskInfo, photos)
          },
          {
            type: 'sub-title',
            label: '耗时统计'
          },
          {
            type: 'tree',
            items: getTimeStatistic(reportInfo)
          },
          {
            type: 'image',
            imageType: 'overview'
          },
          {
            type: 'page-line'  // 分页
          }
        ]
      },
      {
        title: '空三质量报告',
        content: [
          {
            type: 'sub-title',
            label: '测区概况及覆盖度'
          },
          {
            type: 'form',
            items: getSurveyOverview(photos, reportInfo)
          },
          {
            type: 'image',
            imageType: 'overlap'
          },
          {
            type: 'page-line'  // 分页
          },
          {
            type: 'sub-title',
            label: '相机校正'
          },
          {
            type: 'form',
            items: getCameraOverview(photos, reportInfo)
          },
          {
            type: 'cameras',
            items: getCameras(reportInfo)
          },
          {
            type: 'page-line'  // 分页
          },
          {
            type: 'sub-title',
            label: '图像位置'
          },
          {
            type: 'image',
            imageType: 'photos',
            errorFeatures: await getErrorFeatures(photos, taskInfo, reportInfo),
            errorData: getErrorData(reportInfo.image_POS_diff.filter(d => !!d.pos_diff))
          },
          {
            type: 'page-line'  // 分页
          }
        ]
      },
      {
        title: '重建质量报告',
        content: [
          {
            type: 'sub-title',
            label: '运行环境'
          },
          {
            type: 'form',
            items: getReconstructEnvironment(reportInfo, taskInfo)
          },
          {
            type: 'sub-title',
            label: '重建参数'
          },
          {
            type: 'form',
            items: getReconstructParams(reportInfo)
          }
        ]
      }
    ]
  }
  // 建了二维地图才有以下报告
  if (tilejson) {
    report.sections[2].content.push(
      {
        type: 'sub-title',
        label: '数字表面模型'
      },
      {
        type: 'image',
        imageType: 'dsm'
      }
    )
  }
  if (reportInfo.gcp_quality?.length) {
    report.sections[1].content.push({
      type: 'sub-title',
      label: '像控点精度'
    },{
      type: 'third-title',
      label: '控制点'
    },{
      type: 'gcps',
      data: getControlPoints(reportInfo.gcp_quality, 'control point')
    },{
      type: 'third-title',
      label: '检查点'
    },{
      type: 'gcps',
      data: getControlPoints(reportInfo.gcp_quality, 'check point')
    },
    {
      type: 'page-line'  // 分页
    })
  }
  return report
}

function getTaskPreview (taskInfo, photos) {
  return [
    {
      label: '任务名称',
      value: taskInfo.name
    },
    {
      label: '任务类型',
      value: getDataType(taskInfo.data_type)
    },
    {
      label: '数据采集时间',
      value: formatDate(taskInfo.captured_at)
    },
    {
      label: '照片数量',
      value: photos.length
    },
    {
      label: '开始时间',
      value: formatDate(taskInfo.started_at)
    },
    {
      label: '完成时间',
      value: formatDate(taskInfo.completed_at)
    }
  ]
}

function getTimeStatistic (reportInfo) {
  return [
    {
      level: 1,
      label: '总耗时',
      value: formatDuration(((+reportInfo.at_time || 0) + (+reportInfo.reconstruction_time || 0)) * 60),
      children: [
        {
          level: 2,
          label: '空三时间',
          value: formatDuration(reportInfo.at_time * 60),
          children: [
            {
              level: 3,
              label: '特征提取时间',
              value: formatDuration(reportInfo.feature_extraction_time * 60)
            },
            {
              level: 3,
              label: 'sfm时间',
              value: formatDuration(reportInfo.sfm_time * 60)
            }
          ]
        },
        {
          level: 2,
          label: '重建时间',
          value: formatDuration(reportInfo.reconstruction_time * 60)
        }
      ]
    }
  ]
}

function formatDuration (seconds) { // 单位为秒, 格式化为可读的时间
  if (!seconds) {
    return '暂无'
  }
  let duration = moment.duration(seconds, 'seconds')
  if (duration.days() > 0) {
    return `${duration.days()}天${duration.hours()}小时${duration.minutes()}分${duration.seconds()}秒`
  } else if (duration.hours() > 0) {
    return `${duration.hours()}小时${duration.minutes()}分钟${duration.seconds()}秒`
  } else if (duration.minutes() > 0) {
    return `${duration.minutes()}分钟${duration.seconds()}秒`
  } else {
    return `${duration.seconds()}秒`
  }
}

function getDataType (type) {
  if (type === 'infrared') {
    return '热红外'
  } else if (type === 'multispectral') {
    return '多光谱'
  } else {
    return '可见光'
  }
}

function getSurveyOverview (photos, reportInfo) {
  return [
    {
      label: '照片数量',
      value: photos.length
    },
    {
      label: '飞行高度',
      value: reportInfo.flight_height ? `${_.round(reportInfo.flight_height, 2)}米` : '暂无'
    },
    {
      label: '连接点数',
      value: reportInfo.tie_point_count
    },
    {
      label: '地面分辨率',
      value: reportInfo.scene_gsd ? `${_.round(reportInfo.scene_gsd, 2)}米` : '暂无'
    },
    {
      label: '测区面积',
      value: reportInfo.scene_area ? `${_.round(reportInfo.scene_area, 2)}平方米` : '暂无'
    },
    {
      label: '重投影误差',
      value: reportInfo.residual_rmse ? `${_.round(reportInfo.residual_rmse, 2)}pixel` : '暂无'
    }
  ]
}

function getCameraOverview (photos, reportInfo) {
  return [
    {
      label: '相机数量',
      value: reportInfo.input_camera_count || '暂无'
    },
    {
      label: '照片数量',
      value: photos.length
    },
    {
      label: '入网照片',
      value: reportInfo.input_image_count - reportInfo.removed_image_count
    },
    {
      label: '未入网照片',
      value: reportInfo.removed_image_count
    },
    {
      label: '入网率',
      value: `${_.round(100 * (reportInfo.input_image_count - reportInfo.removed_image_count) / reportInfo.input_image_count, 2)}%`
    }
  ]
}

function getControlPoints (gcps, type) {
  let points = gcps.filter(d => d.usage === type)
  let cols = [
    {
      title: 'ID',
      key: 'id',
      width: 80
    },
    {
      title: '刺点数',
      key: 'observation_count'
    },
    {
      title: '重投影误差（px）',
      key: 'residual',
      width: 150
    },
    {
      title: 'X误差（cm）',
      key: 'error_x'
    },
    {
      title: 'Y误差（cm）',
      key: 'error_y'
    },
    {
      title: 'Z误差（cm）',
      key: 'error_z'
    },
    {
      title: '总计',
      key: 'error_total'
    }
  ]
  let data = [], x_norm = 0, y_norm = 0, z_norm = 0, count = 0
  if (!points.length) {
    return {cols, data}
  }
  points.forEach(p => {
    let {id, observation_count, error, observation_residual} = p
    data.push({
      id,
      observation_count,
      residual: observation_residual ? _.round(getAverageValue(observation_residual, 'residual'), 5) : undefined,
      error_x: _.round(error[0] * 100, 5),
      error_y: _.round(error[1] * 100, 5),
      error_z: _.round(error[2] * 100, 5),
      error_total: _.round(Math.sqrt(error[0] * error[0] + error[1] * error[1] + error[2] * error[2]) * 100, 5)
    })
    x_norm += error[0] * error[0]
    y_norm += error[1] * error[1]
    z_norm += error[2] * error[2]
    count += observation_count
  })
  data.push({
    id: '总计',
    observation_count: count,
    error_x: _.round(Math.sqrt(x_norm / points.length) * 100, 5),
    error_y: _.round(Math.sqrt(y_norm / points.length) * 100, 5),
    error_z: _.round(Math.sqrt(z_norm / points.length) * 100, 5),
    error_total: _.round(Math.sqrt((x_norm + y_norm + z_norm) / points.length) * 100, 5)
  })
  return {cols, data}
}

function getCameras (reportInfo) {
  let {AT_camera_parameters, initial_camera_parameters} = reportInfo
  let cameras = []
  let calibrationCols = [
    {
      title: '',
      key: 'name',
      width: 80
    },
    {
      title: '焦距[px]',
      key: 'focalLength'
    },
    {
      title: '主点X[px]',
      key: 'ppX'
    },
    {
      title: '主点Y[px]',
      key: 'ppY'
    },
    {
      title: 'K1',
      key: 'k1'
    },
    {
      title: 'K2',
      key: 'k2'
    },
    {
      title: 'K3',
      key: 'k3'
    },
    {
      title: 'P1',
      key: 'p1'
    },
    {
      title: 'P2',
      key: 'p2'
    }
  ]
  AT_camera_parameters.forEach((item) => {
    cameras.push({
      cameraId: item.id,
      forms: [
        {
          label: '相机名称',
          value: item.camera_name || '暂无'
        },
        {
          label: '成像模式',
          value: '暂无'
        },
        {
          label: '传感器尺寸',
          value: '暂无'
        },
        {
          label: '像幅大小',
          value: `${item.width} × ${item.height}`
        }
      ],
      calibrationCols,
      calibrationData: getCameraCalibrationData(item.id, AT_camera_parameters, initial_camera_parameters, calibrationCols)
    })
  })
  return cameras
}

function getCameraCalibrationData (camera_id, AT_camera_parameters, initial_camera_parameters, columns) {
  let data = ['校正前', '校正后', '差值'].map(d => {
    return {name: d}
  })
  let initialCamera = initial_camera_parameters.find(d => d.id === camera_id)
  let atCamera = AT_camera_parameters.find(d => d.id === camera_id)
  if (!initialCamera || !atCamera) {
    return data
  }
  for(let i = 0;i < columns.length - 1;i++) {
    let precition = i > 5 ? 6 : 4
    data[0][columns[i + 1].key] = _.round(initialCamera.parameters[i], precition)
    data[1][columns[i + 1].key] = _.round(atCamera.parameters[i], precition)
    data[2][columns[i + 1].key] = _.round(atCamera.parameters[i] - initialCamera.parameters[i], precition)
  }
  return data
}

// 照片位置的中误差
function getErrorData (diffs) {
  let quadraticSumX = 0,quadraticSumY = 0,quadraticSumZ = 0
  let n = diffs.length
  diffs.forEach(d => {
    quadraticSumX += Math.pow(d.pos_diff[0], 2)
    quadraticSumY += Math.pow(d.pos_diff[1], 2)
    quadraticSumZ += Math.pow(d.pos_diff[2], 2)
  })
  let err = {
    ex: _.round(Math.sqrt(quadraticSumX / n) * 100, 5),
    ey: _.round(Math.sqrt(quadraticSumY / n) * 100, 5),
    ez: _.round(Math.sqrt(quadraticSumZ / n) * 100, 5),
    exy: _.round(Math.sqrt((quadraticSumX + quadraticSumY) / n) * 100, 5),
    et: _.round(Math.sqrt((quadraticSumX + quadraticSumY + quadraticSumZ) / n) * 100, 5)
  }
  let unit = 'cm'
  if (Object.keys(err).find(k => err[k] > 100)) {
    unit = 'm'
    for(let key in err) {
      err[key] = _.round(err[key] / 100, 5)
    }
  }
  return {
    data: [err],
    cols: [
      {
        title: `X中误差（${unit}）`,
        key: 'ex'
      },
      {
        title: `Y中误差（${unit}）`,
        key: 'ey'
      },
      {
        title: `Z中误差（${unit}）`,
        key: 'ez'
      },
      {
        title: `XY中误差（${unit}）`,
        key: 'exy'
      },
      {
        title: `总中误差（${unit}）`,
        key: 'et'
      }
    ]
  }
}

async function getErrorFeatures (photos, reportInfo, tilejson) {
  let {image_POS_diff} = reportInfo
  let {bounds} = tilejson
  let geoDiff = Math.max(bounds[2] - bounds[0], bounds[3] - bounds[1])
  let zoom = Math.log2(690 * 360 / (geoDiff * 512))
  return Object.assign({bounds}, getFeatures(photos, image_POS_diff.filter(d => !!d.pos_diff), zoom))
}

function getReconstructEnvironment (reportInfo, taskInfo) {
  return [
    {
      label: 'CPU型号',
      value: reportInfo.cpu_name || '暂无'
    },
    {
      label: 'GPU型号',
      value: reportInfo.gpu_name || '暂无'
    },
    {
      label: '内存',
      value: reportInfo.max_ram ? `${_.round(reportInfo.max_ram, 2)}GB` : '暂无'
    },
    {
      label: 'SDK版本（空三）',
      value: reportInfo.sdk_version_AT || '暂无'
    },
    {
      label: 'SDK版本（重建）',
      value: reportInfo.sdk_version_3D || '暂无'
    },
    {
      label: '软件版本',
      value: getAppVersion(taskInfo)
    }
  ]
}

function getReconstructParams (reportInfo) {
  return [
    {
      label: '重建精度',
      value: getReconstructAccurency(reportInfo.reconstruction_level)
    },
    {
      label: '分块数量',
      value: reportInfo.block_count || '暂无'
    },
    {
      label: '输出成果类型',
      value: reportInfo.production_type || '暂无'
    },
    {
      label: '2D成果坐标系',
      value: getCoordinateSystem(reportInfo.production_cs_2d)
    },
    {
      label: '3D成果坐标系',
      value: getCoordinateSystem(reportInfo.production_cs_3d)
    }
  ]
}

function getReconstructAccurency (accurency) {
  accurency = Number(accurency)
  if (accurency === 1) {
    return '超高精度'
  } else if (accurency === 2) {
    return '高精度'
  } else if (accurency === 3) {
    return '中精度'
  } else {
    return '暂无'
  }
}

function getCoordinateSystem (cs) {
  if (!cs) {
    return '暂无'
  }
  let enums = ['LOCAL_ENU', 'LOCAL', 'Geographic', 'Projected', 'Geocentric', 'Unsupported']
  if (cs.epsg_code) {
    return `EPSG:${cs.epsg_code}`
  } else {
    return enums[cs.type]
  }
}