<template >
  <div class="nglviewer" v-click-outside="cancelSelection">
    <div class="control-panel">
      <div class="protein-three-d" v-if="getScaffold != null">
        <div>
          Scaffold:
          <span class="protein-name censor">
            {{ getScaffold.displayName }}:{{ getScaffold.chain }}
          </span>
        </div>
        <ViewIn3dButton
          @click="setThreeDProtein({ protein: getScaffold, flexibility: null })"
        />
      </div>
      <div class="protein-three-d" v-if="getInsert != null">
        <div>
          Insert:
          <span class="protein-name censor">
            {{ getInsert.displayName }}:{{ getInsert.chain }}
          </span>
        </div>
        <ViewIn3dButton
          @click="setThreeDProtein({ protein: getInsert, flexibility: null })"
        />
      </div>
    </div>

    <div id="stage" example-act="3d"></div>
    <SegmentCreator
      v-if="getCurrentWorkflowStep.id == 'loop-exploration'"
      :protein="protein"
      class="threed-segment-creator"
      :acids="currentlySelected"
      @confirm="segmentProtein"
      @cancel="cancelSelection"
    />
    <SecondaryStructureEditor
      v-if="getCurrentWorkflowStep.id == 'protein-selection'"
      :protein="protein"
      class="threed-segment-creator"
      :acids="currentlySelected"
    />
  </div>
</template>

<script>
import { Stage, Selection, ColormakerRegistry } from "ngl";
import { mapGetters, mapMutations, mapActions } from "vuex";
import SegmentCreator from "./SegmentCreator";
import SecondaryStructureEditor from "./ProteinSelection/SecondaryStructreEditor";
import ViewIn3dButton from "./PanelButtons/ViewIn3dButton";
import { color } from "d3";
import * as d3 from "d3";
import ClickOutside from "vue-click-outside";

function secondaryStructureToNglUint(secondary_structure) {
  return secondary_structure.toLowerCase().charCodeAt(0);
}

export default {
  data() {
    return {
      protein: null,
      currentlySelected: null,
      sele: "",
      stage: null,
      structure: null,
      proteinRepresentation: null,
      loopRepresentation: null,
      flexibilityRepresentation: null,
      hoverSchemeId: null,
      regularSchemeId: null,
    };
  },

  components: {
    SegmentCreator,
    SecondaryStructureEditor,
    ViewIn3dButton,
  },

  methods: {
    ...mapMutations(["setPreview", "setThreeDProtein"]),
    ...mapActions(["addCustomLoop"]),
    cancelSelection() {
      if (this.currentlySelected != null) {
        this.$store.commit("cancelSelection");
        this.currentlySelected = null;
      }
    },
    select(index) {
      let selection = this.$store.dispatch("select", {
        protein: this.protein,
        acid: this.protein.acids[index],
      });

      let pvp = this;

      selection.then((s) => {
        if (s != null) {
          pvp.currentlySelected = {
            first: s.from,
            last: s.to,
            selection: s,
          };
        }
      });
    },

    zoomOnPrincipalOfSele() {
      let selection = new Selection(this.sele.normal);
      // this.proteinRepresentation.setSelection(this.sele.inverse);
      this.flexibilityRepresentation.setParameters({
        opacity: 0,
      });
      this.proteinRepresentation.setParameters({
        opacity: 0.4,
      });
      this.loopRepresentation.setSelection(this.sele.normal);
      this.loopRepresentation.setParameters({
        opacity: 1,
        aspectRatio: 3.0,
        radiusScale: 1.2,
      });

      let pa = this.structure.structure.getPrincipalAxes(selection);
      this.structure.autoView(this.sele.normal, 1500);
      this.stage.animationControls.rotate(pa.getRotationQuaternion(), 1500);
    },

    loopToSele(loop) {
      return {
        normal: `:${loop.chain} and ${loop.start}-${loop.end}/0`,
        inverse: `:${loop.chain} and not ${loop.start}-${loop.end}/0`,
      };
    },

    displayStructure(structure) {
      this.structure = structure;
      this.regularSchemeId = ColormakerRegistry.addSelectionScheme(
        [[color(this.protein.color).formatHex(), "*"]],
        "Viewing"
      );
      this.proteinRepresentation = structure.addRepresentation("cartoon", {
        color: this.regularSchemeId,
        roughness: 1,
      });
      this.loopRepresentation = structure.addRepresentation("cartoon", {
        color:
          this.getScaffold?.name == this.protein.name ? "#ff3d23" : "#179BAC",
        roughness: 1,
      });
      this.flexibilityRepresentation = structure.addRepresentation("cartoon", {
        color: "#000000",
        roughness: 1,
      });
      this.defaultView();
    },

    displayFlexibility(type) {
      function findMaximumAbsoluteBfactor(bfactorList, types) {
        let maximum = 0;
        for (let bfactor of bfactorList) {
          for (let type of types) {
            if (maximum < Math.abs(bfactor[type])) {
              maximum = Math.abs(bfactor[type]);
            }
          }
        }
        return maximum;
      }
      let bfactors = this.getBFactorsByProtein(this.protein.name);
      this.absolute_maximum = findMaximumAbsoluteBfactor(
        bfactors.values,
        bfactors.types
      );
      let scale = d3
        .scaleLinear()
        .domain([-this.absolute_maximum, 0, this.absolute_maximum])
        .interpolate(d3.interpolateRgb.gamma(1.5))
        .range(["#1f77b4", "#999", "#ff7f0e"]);

      let flexibilitySchemeID = ColormakerRegistry.addScheme(function () {
        this.atomColor = function (atom) {
          let resColor = color(scale(bfactors.values[atom.residueIndex][type]));
          let resColorHexed = parseInt(resColor.formatHex().slice(1), 16);
          return resColorHexed;
        };
      });

      this.flexibilityRepresentation = this.structure.addRepresentation(
        "cartoon",
        {
          color: flexibilitySchemeID,
        }
      );

      this.flexibilityRepresentation.setParameters({
        opacity: 1,
      });
      this.proteinRepresentation.setParameters({
        opacity: 0,
      });
      this.loopRepresentation.setParameters({
        opacity: 0,
      });
      this.structure.autoView(500);
    },

    defaultView() {
      this.proteinRepresentation.setParameters({
        opacity: 1,
        side: "front",
        smoothSheet: true,
        // tension: 0
      });
      this.loopRepresentation.setParameters({
        opacity: 0,
        side: "front",
        smoothSheet: true,
        // tension: 0
      });
      this.flexibilityRepresentation.setParameters({
        opacity: 0,
        side: "front",
        smoothSheet: true,
      });
      this.structure.autoView(500);
    },

    segmentProtein({ name, from, to }) {
      this.addCustomLoop({
        proteinName: this.protein.name,
        region: { name, from, to },
      });
      this.currentlySelected = null;
    },
    zoomToLoop(loop) {
      this.sele = this.loopToSele(loop);
      this.zoomOnPrincipalOfSele();
    },

    updateSecondaryStructures() {
      if (
        this.structure != null &&
        this.structure.structure.residueStore != null
      ) {
        let sstructs = this.structure.structure.residueStore.sstruc;
        for (let acid of this.protein.acids) {
          sstructs[acid.index] = secondaryStructureToNglUint(
            acid.simple_coils_secondary_structure
          );
        }
      }
    },

    _click(clickingProxy) {
      if (clickingProxy && (clickingProxy.atom || clickingProxy.bond)) {
        let atom = clickingProxy.atom || clickingProxy.closestBondAtom;
        this.select(atom.residueIndex);
      } else if (clickingProxy) {
        this.cancelSelection();
      }
    },

    _hover(hoverProxy) {
      if (hoverProxy && (hoverProxy.atom || hoverProxy.bond)) {
        let atom = hoverProxy.atom || hoverProxy.closestBondAtom;
        let hoverSele = "";
        //tooltip

        let acid = this.protein.getAcid(atom.resno);
        if (acid != null) {
          this.setPreview({
            description: {
              Acid: acid.acid_name,
              "PDB n.": acid.pdb_number,
              Index: acid.index,
              DSSP: acid.secondary_structure,
            },
          });
        }

        //oh wow the names of the selection-related properties are confuzing AF
        if (this.getCurrentSelection.from != null) {
          // only started selection
          let from = this.getCurrentSelection.from.pdb_number;
          let to = atom.resno;
          if (from > to) {
            [from, to] = [to, from];
          }
          hoverSele = `${from}-${to}`;
        } else if (this.currentlySelected != null) {
          // selection ended
          hoverSele = `${this.currentlySelected.first.pdb_number}-${this.currentlySelected.last.pdb_number} or ${atom.resno}`;
        } else {
          // no selection in progress
          hoverSele = `${atom.resno}`;
        }
        this.hoverSchemeId = ColormakerRegistry.addSelectionScheme(
          [
            [color(this.protein.color).brighter(1.6).formatHex(), hoverSele],
            [color(this.protein.color).formatHex(), "*"],
          ],
          "Hover"
        );
        this.proteinRepresentation.setParameters({
          colorScheme: this.hoverSchemeId,
        });
      } else {
        this.proteinRepresentation?.setParameters({
          colorScheme: this.regularSchemeId,
        });
        this.setPreview(null);
      }
    },
  },

  mounted: function () {
    let viewPanel = this;
    this.$nextTick().then(function () {
      viewPanel.stage = new Stage("stage", {
        backgroundColor: "white",
        tooltip: false,
      });
      viewPanel.stage.signals.clicked.add(viewPanel._click);
      viewPanel.stage.signals.hovered.add(viewPanel._hover);
      // viewPanel.stage.viewer.renderer.domElement.removeAttribute("tabindex"); //don't zoom when scrolling
    });
  },

  computed: {
    ...mapGetters([
      "getZoomedLoop",
      "getThreeDProtein",
      "get3dView",
      "getCurrentSelection",
      "getBFactorsByProtein",
      "getScaffold",
      "getInsert",
      "getCurrentWorkflowStep",
    ]),
  },

  watch: {
    "protein.selection": {
      handler: function () {
        if (
          this.protein?.selection == null ||
          this.protein.selection.length == 0
        ) {
          this.regularSchemeId = ColormakerRegistry.addSelectionScheme(
            [[color(this.protein.color).formatHex(), "*"]],
            "Viewing"
          );
        } else {
          let firstIndex = this.protein.selection[0];
          let lastIndex =
            this.protein.selection[this.protein.selection.length - 1];

          let first = this.protein.getAcidByIndex(firstIndex);
          let last = this.protein.getAcidByIndex(lastIndex);

          this.regularSchemeId = ColormakerRegistry.addSelectionScheme(
            [
              [
                color(this.protein.color).brighter(1.4).formatHex(),
                `${first.pdb_number}-${last.pdb_number}`,
              ],
              [color(this.protein.color).formatHex(), "*"],
            ],
            "Hover"
          );
        }
        this.proteinRepresentation?.setParameters({
          colorScheme: this.regularSchemeId,
        });
      },
    },
    "protein.acids": {
      handler: function () {
        if (this.protein == null) {
          return;
        }
        if (this.protein.loops.length > 0) {
          return;
        }
        this.updateSecondaryStructures();
        this.stage.removeAllComponents();
        this.stage
          .loadFile(new Blob([this.protein.pdb], { type: "text/plain" }), {
            ext: "pdb",
          })
          .then((structure) => {
            this.structure = structure;
            this.updateSecondaryStructures();
            structure.setSelection(`:${this.protein.chain}/0`);
            this.displayStructure(structure);
          });
      },
      deep: true,
    },
    // ok this is ugly as fuck
    // but how should I resolve the fact
    // that stuff like autoView or setParameters
    // behaves like async but is not thenable.
    get3dView(view) {
      if (view.protein == null) {
        this.protein = view.protein;
        this.stage.removeAllComponents();
      } else if (this.protein != view.protein || view.flexibility) {
        this.protein = view.protein;
        this.stage.removeAllComponents();
        this.loading = this.stage
          .loadFile(new Blob([this.protein.pdb], { type: "text/plain" }), {
            ext: "pdb",
          })
          .then((structure) => {
            this.structure = structure;
            this.updateSecondaryStructures();
            structure.setSelection(`:${this.protein.chain}/0`);
            this.displayStructure(structure);

            if (view.loop != null) {
              this.zoomToLoop(view.loop);
            }

            if (view.flexibility != null) {
              this.displayFlexibility(view.flexibility);
            }
          });
      } else {
        if (view.loop == null) {
          this.defaultView();
        } else {
          this.zoomToLoop(view.loop);
        }
      }
    },

    currentlySelected(selection) {
      if (selection == null) {
        this.regularSchemeId = ColormakerRegistry.addSelectionScheme(
          [[color(this.protein.color).formatHex(), "*"]],
          "Viewing"
        );
      } else {
        this.regularSchemeId = ColormakerRegistry.addSelectionScheme(
          [
            [
              color(this.protein.color).brighter(1.4).formatHex(),
              `${this.currentlySelected.first.pdb_number}-${this.currentlySelected.last.pdb_number}`,
            ],
            [color(this.protein.color).formatHex(), "*"],
          ],
          "Hover"
        );
      }
      this.proteinRepresentation?.setParameters({
        colorScheme: this.regularSchemeId,
      });
    },

    getSyncedSelection(selection) {
      if (selection == null) {
        this.regularSchemeId = ColormakerRegistry.addSelectionScheme(
          [[color(this.protein.color).formatHex(), "*"]],
          "Viewing"
        );
      } else {
        this.regularSchemeId = ColormakerRegistry.addSelectionScheme(
          [
            [
              color(this.protein.highlightColor).formatHex(),
              `${this.currentlySelected.first.pdb_number}-${this.currentlySelected.last.pdb_number}`,
            ],
            [color(this.protein.color).formatHex(), "*"],
          ],
          "Hover"
        );
      }
      this.proteinRepresentation?.setParameters({
        colorScheme: this.regularSchemeId,
      });
    },
    // getThreeDProtein(protein) {
    //   if (this.protein != protein) {
    //     this.protein = protein;
    //     this.stage.removeAllComponents();
    //     this.loading = this.stage
    //       .loadFile(new Blob([protein.pdb], { type: "text/plain" }), {
    //         ext: "pdb"
    //       })
    //       .then(this.displayStructure);
    //   }
    // }
  },
  directives: {
    ClickOutside,
  },
};
</script>

<style scoped>
#stage {
  width: 450px;
  height: 400px;
  touch-action: none;
  border-radius: 10px;
}
.threed-segment-creator {
  position: relative;
  margin-top: -24px;
  max-width: 380px;
  background: white;
}
.nglviewer {
  display: flex;
  flex-direction: column;
  align-items: center;
  touch-action: none;
  /* border: var(--primary-color) 1px solid; */
}
.control-panel {
  position: absolute;
  /* height: 0px; */
  z-index: 2;
  margin-right: -257px;
  padding-top: 2px;
  padding-left: 10px;
  padding-right: 3px;

  border-radius: 0px 10px 0px 10px;
  background: rgba(255, 255, 255, 0.9);
}

.protein-three-d {
  display: flex;
  justify-content: space-between;
  width: 180px;
  padding-top: 2px;
  padding-bottom: 2px;
}
</style>