/* vim: set ts=2 sts=2 sw=2 expandtab: */

const WATNA_BASES = [ 'A', 'C', 'G', 'T' ];
const WATNA_NTCS = [
  'AA00', 'AA01', 'AA02', 'AA03', 'AA04', 'AA05', 'AA06', 'AA07', 'AA08', 'AA09', 'AA10', 'AA11', 'AA12', 'AA13', 'AAS1',
  'AB01', 'AB02', 'AB03', 'AB04', 'AB05', 'AB1S', 'AB2S', 'BA01', 'BA05', 'BA08', 'BA09', 'BA10', 'BA13', 'BA16', 'BA17',
  'BB00', 'BB01', 'BB02', 'BB03', 'BB04', 'BB05', 'BB07', 'BB08', 'BB10', 'BB11', 'BB12', 'BB13', 'BB14', 'BB15', 'BB16', 'BB17', 'BB1S', 'BB20', 'BB2S', 'BBS1',
  'IC01', 'IC02', 'IC03', 'IC04', 'IC05', 'IC06', 'IC07',
  'OP01', 'OP02', 'OP03', 'OP04', 'OP05', 'OP06', 'OP07', 'OP08', 'OP09', 'OP10', 'OP11', 'OP12', 'OP13', 'OP14', 'OP15', 'OP16', 'OP17', 'OP18', 'OP19', 'OP1S', 'OP20', 'OP21', 'OP22', 'OP23', 'OP24', 'OP25', 'OP26', 'OP27', 'OP28', 'OP29', 'OP30', 'OP31', 'OPS1',
  'ZZ01', 'ZZ02', 'ZZ1S', 'ZZ2S', 'ZZS1', 'ZZS2'
];

let _DONT_TOUCH_preloaded = false;
let _DONT_TOUCH_fragment_paths_cache = {};
let fragments_data = null;

function _mkNtcPaths(ntc, seq) {
  const fragId = mkFragId(ntc, seq);
  const mkPath = name => `${ntc}/${seq}/${fragId}_${name}`;
  return {
    root: './data',
    structures: {
      reference: mkPath('reference_scale.pdb'),
      base: mkPath('base_waterpeaks.pdb'),
      nucleotide: mkPath('dinu_waterpeaks.pdb'),
      phosphate: mkPath('phos_waterpeaks.pdb'),
    },
    densityMaps: {
      base: mkPath('base_water.map.ccp4'),
      nucleotide: mkPath('dinu_water.map.ccp4'),
      phosphate: mkPath('phos_water.map.ccp4'),
    }
  };
}

function _mkStructurePaths(structId) {
  const mkPath = name => `${structId}/${name}`;
  return {
    root: './data/predictions',
    structures: {
      reference: mkPath(`${structId}_origDNA_with_symRelWaters.pdb`),
      base: mkPath('base_waterpeaks.pdb'),
      nucleotide: mkPath('nucl_waterpeaks.pdb'),
      phosphate: mkPath('phos_waterpeaks.pdb'),
    },
    densityMaps: {
      base: mkPath('base_water.map.ccp4'),
      nucleotide: mkPath('nucl_water.map.ccp4'),
      phosphate: mkPath('phos_water.map.ccp4'),
    },
  };
}

function _watna_check_waters(obj) {
  if (typeof obj !==  'object')
    return false;

  for (const key in obj) {
    if (typeof key !== 'string')
      return false;

    if (!WATNA_NTCS.includes(key))
      return false;

    const inner = obj[key];
    if (typeof inner !== 'object')
      return false;

    for (const ikey in inner) {
      const blocks = ikey.split('_');
      if (blocks.length !== 2)
        return false;

      if (!WATNA_BASES.includes(blocks[0]) || !WATNA_BASES.includes(blocks[1]))
        return false;

      const val = inner[ikey];
      if (typeof val !== 'number')
        return false;

      if (val < 0)
        return false;
    }
  }

  return true;
}

/* https://alienryderflex.com/hsp.html */
function _watna_luminance(r, g, b) {
  r /= 255.0;
  g /= 255.0;
  b /= 255.0;
  return Math.sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b);
}

function _watna_fg_color(r, g, b) {
  return _watna_luminance(r, g, b) < 0.57 ? 'white' : 'black';
}

async function watna_browse_add(instId, ntc, seq) {
  try {
    await WVApi.add(instId, mkFragId(ntc, seq), _mkNtcPaths(ntc, seq), [ 'reference', 'base', 'phosphate' ], [ 'base', 'phosphate' ]);
  } catch (e) {
    console.log(e);
    throw e;
  }
}

async function watna_fetch_data() {
  try {
    const resp = await fetch('fragments_data.json');
    if (!resp.ok)
      throw new Error(`Cannot fetch water data: ${resp.statusText}`);

    const json = await resp.json();
    if (json.threshold_hard === undefined ||
        json.threshold_soft === undefined ||
        json.waters === undefined) {
      throw new Error('Invalid fragments_data format');
    }

    if (typeof json.threshold_hard !== 'number' ||
        typeof json.threshold_hard !== 'number' ||
        !_watna_check_waters(json.waters)) {
      throw new Error('Invalid fragments_data format');
    }

    if (json.threshold_hard < 0 || json.threshold_soft <= json.threshold_hard) {
      throw new Error('Invalid fragments_data format');
    }

    fragments_data = {
      getSoftThreshold: () => {
        const thresh = json.threshold_soft;
        return thresh;
      },
      getHardThreshold: () => {
        const thresh = json.threshold_hard;
        return thresh;
      },
      getWaters: (ntc, seq) => {
        const waters = json.waters;
        const ret = waters[ntc][seq];
        if (ret === undefined)
          throw new Error('Invalid NtC/seq combination');
        return ret;
      }
    };
  } catch (e) {
    throw e;
  }
}

function mkFragId(ntc, seq) {
  return `${ntc}_${seq}`;
}

async function watna_fragment_clicked(fragElem, instId, ntc, seq, referenceNameText, errorReportCallback) {
  if (!_DONT_TOUCH_preloaded)
    return;

  const fragId = mkFragId(ntc, seq);
  if (WVApi.has(instId, fragId)) {
    await WVApi.remove(instId, fragId);
  } else {
    try {
      await WVApi.add(instId, fragId, { text: referenceNameText, transform: true }, _mkNtcPaths(ntc, seq), [ 'reference', 'base', 'phosphate' ], [ 'base', 'phosphate' ]);
      watna_set_elem_color(fragElem, instId, fragId);
    } catch (e) {
      console.log(e);
      if (errorReportCallback) {
        errorReportCallback(e);
      }
    }
  }
}

async function watna_structure_clicked(fragElem, instId, structId, referenceName, errorReportCallback) {
  const fragId = structId; // Seemingly useless but what if we change this later?

  if (WVApi.has(instId, fragId)) {
    await WVApi.remove(instId, fragId);
  } else {
    try {
      await WVApi.add(instId, fragId, referenceName, _mkStructurePaths(structId), [ 'reference', 'base', 'phosphate' ], [ 'base', 'phosphate' ]);
      watna_set_elem_color(fragElem, instId, fragId);
    } catch (e) {
      console.log(e);
      if (errorReportCallback) {
        errorReportCallback(e);
      }
    }
  }
}

function watna_init(instId, configuration) {
  WVApi.init(instId, configuration);
}

async function watna_preload(instId, ntcs, progressCallback) {
  if (fragments_data === null)
    throw new Error('Fragments data have not been loaded yet');

  const toPreload = [];

  const sequences = watna_sequences();
  const thrHard = fragments_data.getHardThreshold();
  const getWaters = fragments_data.getWaters;
  for (const ntc of ntcs) {
    for (const seq of sequences) {
      const waterCnt = getWaters(ntc, seq);
      if (waterCnt >= thrHard) {
        const fragId = mkFragId(ntc, seq);
        toPreload.push({ fragId, paths: _mkNtcPaths(ntc, seq) });
      }
    }
  }

  try {
    await WVApi.load(instId, toPreload, progressCallback);
  } catch (errors) {
    console.log(errors);
  } finally {
    _DONT_TOUCH_preloaded = true;
  }
}

function watna_rerender(instId) {
    WVApi.forceRerender(instId);
}

function watna_sequences() {
  const sequences = [];
  for (const ba of WATNA_BASES) {
    for (const bb of WATNA_BASES) {
      sequences.push(`${ba}_${bb}`);
    }
  }

  return sequences;
}

function watna_set_elem_color(elem, instId, fragId) {
  const colors = WVApi.fragmentColors(instId, fragId, 'rgb');
  if (colors) {
    const [ r, g, b ] = colors.base;
    elem.style.backgroundColor = `rgb(${r}, ${g}, ${b})`;
    elem.style.color = _watna_fg_color(r, g, b);
  }
}

function watna_set_on_colors_changed_callback(instId, callback) {
  WVApi.setOnFragmentColorsChangedCallback(instId, callback);
}

function watna_set_on_removed_callback(instId, callback) {
  WVApi.setOnFragmentRemovedCallback(instId, callback);
}