import { LoggerSingleton } from '../utils/logger';
import { ConfParams, config } from '../utils/config';
import { fileExists } from '../utils/fileHelper';
import dicomParser from 'dicom-parser';
import fs from 'fs';
import path from 'path';

interface ValueType {
  Value: string[] | number[] | unknown[];
  vr: string;
}
type ElementType = Record<string, ValueType>;

function parseFile(filename: string): Promise<ElementType> {
  const logger = LoggerSingleton.Instance;
  return new Promise<ElementType>((resolve, reject) => {
    fileExists(filename).then((success: boolean) => {
      if (!success) {
        logger.error(`file does not exist: ${filename}`);
        return reject();
      }

      fs.promises.readFile(filename).then((data: Uint8Array) => {
        const dataset = dicomParser.parseDicom(data);

        // parse additional needed attributes
        const patientName = dataset.string('x00100010');
        const patentID = dataset.string('x00100020');
        const studyInstanceUID = dataset.string('x0020000d');
        const studyDate = dataset.string('x00080020');
        const studyTime = dataset.string('x00080030');
        const seriesInstanceUID = dataset.string('x0020000e');
        const seriesNumber = dataset.string('x00200011');
        const sopInstanceUID = dataset.string('x00080018');
        const sopClassUID = dataset.string('x00080016');
        const bitsAllocated = dataset.uint16('x00280100');
        const bitsStored = dataset.uint16('x00280101');
        const highBit = dataset.uint16('x00280102');
        const rows = dataset.uint16('x00280010');
        const cols = dataset.uint16('x00280011');
        const pixelSpacingString = dataset.string('x00280030');
        const pixelSpacing = pixelSpacingString ? pixelSpacingString.split('\\').map((e: string) => parseFloat(e)) : [1, 1];
        const modality = dataset.string('x00080060');
        const samplesPerPixel = dataset.uint16('x00280002');
        const photometricInterpretation = dataset.string('x00280004');
        const pixelRepresentation = dataset.uint16('x00280103');
        const windowCenter = dataset.string('x00281050');
        const wc = windowCenter ? parseFloat(windowCenter.split('\\')[0]) : 40;
        const windowWidth = dataset.string('x00281051');
        const ww = windowWidth ? parseFloat(windowWidth.split('\\')[0]) : 80;
        const rescaleIntercept = parseFloat(dataset.string('x00281052') || '1');
        const rescaleSlope = parseFloat(dataset.string('x00281053') || '1');
        const iopString = dataset.string('x00200037');
        const iop = iopString ? iopString.split('\\').map((e: string) => parseFloat(e)) : null;
        const ippString = dataset.string('x00200032');
        const ipp = ippString ? ippString.split('\\').map((e: string) => parseFloat(e)) : null;
        const instanceNumber = dataset.string('x00200013');
        const sliceThickness = dataset.string('x00180050');
        const sliceLocation = dataset.string('x00201041');

        // TODO: buat ini dynamic berdasarkan includefields atau modality
        // TODO: hapus tambahan di bawah ini yang tidak diperlukan

        // MR-specific tags for overlay
        const spacingBetweenSlices = dataset.string('x00180088');
        const percentPhaseFieldOfView = dataset.string('x00180094');
        const fovDimensionElement = dataset.elements.x00181149;
        const fovDimension = fovDimensionElement
          ? [
            dataset.uint16('x00181130', 0),
            dataset.uint16('x00181130', 1),
          ]
          : null;
        const acquisitionMatrixElement = dataset.elements.x00181310;
        const acquisitionMatrix = acquisitionMatrixElement
          ? [
            dataset.uint16('x00181310', 0),
            dataset.uint16('x00181310', 1),
            dataset.uint16('x00181310', 2),
            dataset.uint16('x00181310', 3),
          ]
          : null;
        const scanningSequence = dataset.string('x00180020');
        const repetitionTime = dataset.string('x00180080');
        const echoTime = dataset.string('x00180081');
        const inversionTime = dataset.string('x00180082');
        const receiveCoilName = dataset.string('x00181250');
        const mrAcquisitionType = dataset.string('x00180023');
        const phaseEncodingDirection = dataset.string('x00181312');
        const numOfAverages = dataset.string('x00180083');
        const echoTrainLength = dataset.string('x00180091');
        const flipAngle = dataset.string('x00181314');
        const pixelBandwidth = dataset.string('x00180095');
        const acquisitionTime = dataset.string('x00080032');
        const acquistionDurationTotal = dataset.string('x00189073'); // in seconds
        const acquistionDurationPerFrame = dataset.string('x00189220'); // in ms
        const parallelAcquisitionTechnique = dataset.string('x00181316');

        // append to all results
        const result: ElementType = {
          '00100010': { Value: [{ Alphabetic: patientName }], vr: 'PN' },
          '00100020': { Value: [patentID], vr: 'LO' },
          '0020000D': { Value: [studyInstanceUID], vr: 'UI' },
          '00080020': { Value: [studyDate], vr: 'DA' },
          '00080030': { Value: [studyTime], vr: 'TM' },
          '0020000E': { Value: [seriesInstanceUID], vr: 'UI' },
          '00200011': { Value: [seriesNumber], vr: 'IS' },
          '00080018': { Value: [sopInstanceUID], vr: 'UI' },
          '00080016': { Value: [sopClassUID], vr: 'UI' },
          '00080060': { Value: [modality], vr: 'CS' },
          '00280002': { Value: [samplesPerPixel], vr: 'US' },
          '00280004': { Value: [photometricInterpretation], vr: 'CS' },
          '00280010': { Value: [rows], vr: 'US' },
          '00280011': { Value: [cols], vr: 'US' },
          '00280030': { Value: pixelSpacing, vr: 'DS' },
          '00280100': { Value: [bitsAllocated], vr: 'US' },
          '00280101': { Value: [bitsStored], vr: 'US' },
          '00280102': { Value: [highBit], vr: 'US' },
          '00280103': { Value: [pixelRepresentation], vr: 'US' },
          '00281050': { Value: [wc], vr: 'DS' },
          '00281051': { Value: [ww], vr: 'DS' },
          '00281052': { Value: [rescaleIntercept], vr: 'DS' },
          '00281053': { Value: [rescaleSlope], vr: 'DS' },
          ...(iop && { '00200037': { Value: iop, vr: 'DS' } }),
          ...(ipp && { '00200032': { Value: ipp, vr: 'DS' } }),
          '00200013': { Value: [instanceNumber], vr: 'IS' },
          '00180050': { Value: [sliceThickness], vr: 'DS' },
          '00201041': { Value: [sliceLocation], vr: 'DS' },
          '00180088': { Value: [spacingBetweenSlices], vr: 'DS' },
          '00180094': { Value: [percentPhaseFieldOfView], vr: 'DS' },
          ...(fovDimension && { '00181149': { Value: fovDimension, vr: 'IS' } }),
          ...(acquisitionMatrix && { '00181310': { Value: acquisitionMatrix, vr: 'US' } }),
          '00180020': { Value: [scanningSequence], vr: 'CS' },
          '00180080': { Value: [repetitionTime], vr: 'DS' },
          '00180081': { Value: [echoTime], vr: 'DS' },
          '00180082': { Value: [inversionTime], vr: 'DS' },
          '00181250': { Value: [receiveCoilName], vr: 'SH' },
          '00180023': { Value: [mrAcquisitionType], vr: 'CS' },
          '00181312': { Value: [phaseEncodingDirection], vr: 'CS' },
          '00180083': { Value: [numOfAverages], vr: 'DS' },
          '00180091': { Value: [echoTrainLength], vr: 'IS' },
          '00181314': { Value: [flipAngle], vr: 'DS' },
          '00180095': { Value: [pixelBandwidth], vr: 'DS' },
          '00080032': { Value: [acquisitionTime], vr: 'TM' },
          '00189073': { Value: [acquistionDurationTotal], vr: 'DS' },
          '00189220': { Value: [acquistionDurationPerFrame], vr: 'FD' },
          '00181316': { Value: [parallelAcquisitionTechnique], vr: 'CS' },
        };

        resolve(result);
      });
    });
  });
}

export function parseMeta(json: object, studyInstanceUID: string, seriesInstanceUID: string): Promise<unknown> {
  const logger = LoggerSingleton.Instance;
  logger.info(`parsing series ${seriesInstanceUID}`);

  const parsing = new Array<Promise<ElementType>>();
  const storagePath = config.get(ConfParams.STORAGE_PATH) as string;
  for (const [key] of Object.entries(json)) {
    const sopInstanceUid = json[key]['00080018'].Value[0];
    const pathname = path.join(storagePath, studyInstanceUID, sopInstanceUid);
    parsing.push(parseFile(pathname));

  }
  return Promise.all(parsing);
}
