<template >
  <div class="nglviewer" @mouseout="setPreview(null)">
    <div id="stage" example-act="3d"></div>

    <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>
          <br />
        </div>
        <HideButton
          :hidden="!isVisible('scaffold')"
          @click="toggleVisibility('scaffold')"
        />
      </div>
      <div
        class="protein-three-d"
        v-if="threeDData['scaffold'].focusedOn != null"
      >
        <div>
          Focus:
          <span class="protein-name censor">
            {{ threeDData["scaffold"].focusedOn }}
          </span>
          <br />
        </div>
        <CancelButton @click="cancelFocus('scaffold')" />
      </div>
      <div class="dividor" />
      <div class="protein-three-d" v-if="getInsert != null">
        <div>
          Insert:
          <span class="protein-name censor">
            {{ getInsert.displayName }}:{{ getInsert.chain }}
          </span>
        </div>
        <HideButton
          :hidden="!isVisible('insert')"
          @click="toggleVisibility('insert')"
        />
      </div>
      <div
        class="protein-three-d"
        v-if="threeDData['insert'].focusedOn != null"
      >
        <div>
          Focus:
          <span class="protein-name censor">
            {{ threeDData["insert"].focusedOn }}
          </span>
          <br />
        </div>
        <CancelButton @click="cancelFocus('insert')" />
      </div>
    </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 "./SecsRefinment/SecondaryStructreEditor";
import HideButton from "./PanelButtons/HideButton";
import CancelButton from "./PanelButtons/CancelButton";
import { color } from "d3";

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

class ThreeDProteinData {
  constructor() {
    this.protein = null;
    this.structureComponent = null;
    this.loopComponent = null;
    this.defaultSchemeId = null;
    this.defaultRepresentation = null;
    this.focusedOn = null;
  }
}
export default {
  data() {
    return {
      stage: null,
      lastHoverRole: null,
      threeDData: {
        scaffold: new ThreeDProteinData(),
        insert: new ThreeDProteinData(),
      },
    };
  },

  components: {
    SegmentCreator,
    SecondaryStructureEditor,
    HideButton,
    CancelButton,
  },

  methods: {
    ...mapMutations(["setPreview", "setThreeDProtein"]),

    _hover(hoverProxy) {
      if (hoverProxy && (hoverProxy.atom || hoverProxy.bond)) {
        let atom = hoverProxy.atom || hoverProxy.closestBondAtom;
        //tooltip
        let role = atom.structure.role;
        this.lastHoverRole = role;
        let protein = this.proteinData[role];
        let acid = protein.getAcid(atom.resno);
        if (acid != null) {
          this.setPreview({
            description: {
              PDB: protein.name,
              Acid: acid.acid_name,
              "PDB n.": acid.pdb_number,
              Index: acid.index,
              DSSP: acid.secondary_structure,
            },
          });
        }

        let hoverSele = null;
        //regular hover
        hoverSele = `${atom.resno}`;

        let hoverSchemeId = ColormakerRegistry.addSelectionScheme(
          [
            [color(protein.color).brighter(5).formatHex(), hoverSele],
            [color(protein.color).formatHex(), "*"],
          ],
          "Hover"
        );

        this.threeDData[role].defaultRepresentation.setParameters({
          colorScheme: hoverSchemeId,
        });
      } else {
        this.threeDData[
          this.lastHoverRole
        ]?.defaultRepresentation.setParameters({
          colorScheme: this.threeDData[this.lastHoverRole]?.defaultSchemeId,
        });
        this.lastHoverRole = null;
        this.setPreview(null);
      }
    },

    // ---------
    updateSecondaryStructures(role) {
      let structureComponent = this.threeDData[role].structureComponent;
      let sstructs = structureComponent.structure.residueStore.sstruc;

      let protein = this.proteinData[role];
      for (let acid of protein.acids) {
        sstructs[acid.index] = secondaryStructureToNglUint(
          acid.simple_coils_secondary_structure
        );
      }
      structureComponent.rebuildRepresentations();
    },

    toggleVisibility(role) {
      let visibility = !this.threeDData[role].structureComponent.visible;
      this.threeDData[role].structureComponent.setVisibility(visibility);
    },

    defaultVisualization(role) {
      this.threeDData[role].structureComponent.setVisibility(true);
      this.threeDData[role].structureComponent.setSelection(
        `:${this.proteinData[role].chain}/0`
      );
      this.threeDData[role].loopComponent.setVisibility(false);

      this.threeDData[role].defaultRepresentation.setParameters({
        color: this.threeDData[role].defaultSchemeId,
        opacity: 1,
      });
    },

    highlight(role, selection) {
      let schemeId = this.threeDData[role].defaultSchemeId;
      if (selection?.first != null && selection?.last != null) {
        schemeId = ColormakerRegistry.addSelectionScheme(
          [
            [
              color(this.proteinData[role].color).brighter(5).formatHex(),
              `${selection.first.pdb_number}-${selection.last.pdb_number}`,
            ],
            [color(this.proteinData[role].color).formatHex(), "*"],
          ],
          "Selection"
        );
      }

      this.threeDData[role].defaultRepresentation.setParameters({
        color: schemeId,
      });
    },

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

    loopHighlight(role, loopId) {
      if (loopId == null) {
        this.defaultVisualization(role);
        return;
      }
      let loop = this.proteinData[role].loops.find((l) => l.id == loopId);

      let roleThreeDData = this.threeDData[role];
      //turn on loop component
      roleThreeDData.loopComponent.setVisibility(true);
      //set selection
      roleThreeDData.loopComponent.setSelection(
        this.loopToSeleString(loop).loop
      );
      roleThreeDData.structureComponent.setSelection(
        this.loopToSeleString(loop).inverse
      );
      //opacity of default
      roleThreeDData.defaultRepresentation.setParameters({
        opacity: 0.3,
      });
    },

    loopZoomIn(role, loopId) {
      let loop = this.proteinData[role].loops.find((l) => l.id == loopId);
      let loopSeleString = this.loopToSeleString(loop).loop;
      let selection = new Selection();

      let loopPrincipalAxis =
        this.threeDData[role].loopComponent.structure.getPrincipalAxes(
          selection
        );
      this.threeDData[role].loopComponent.autoView(loopSeleString, 500);
      this.stage.animationControls.rotate(
        loopPrincipalAxis.getRotationQuaternion(),
        500
      );
    },

    cancelFocus(role) {
      this.threeDData[role].focusedOn = null;
      this.proteinData[role].zoomedLoop = null;
      this.defaultVisualization(role);

      let selection = new Selection();

      // yes, it's supposed to be hard-coded insert - it's aligned to scaffold and centered
      let structurePrincipalAxis =
        this.threeDData["insert"].structureComponent.structure.getPrincipalAxes(
          selection
        );
      this.threeDData["insert"].structureComponent.autoView(500);
      this.stage.animationControls.rotate(
        structurePrincipalAxis.getRotationQuaternion(),
        500
      );
    },

    async initProtein(role) {
      let roleThreeDData = this.threeDData[role];

      roleThreeDData.defaultSchemeId = ColormakerRegistry.addSelectionScheme(
        [[color(this.proteinData[role].color).formatHex(), "*"]],
        "Viewing"
      );

      //default visualization
      roleThreeDData.structureComponent = await this.stage.loadFile(
        new Blob([this.proteinData[role].pdb], { type: "text/plain" }),
        {
          ext: "pdb",
        }
      );

      roleThreeDData.structureComponent.setSelection(
        `:${this.proteinData[role].chain}/0`
      );

      roleThreeDData.defaultRepresentation =
        roleThreeDData.structureComponent.addRepresentation("cartoon", {
          color: roleThreeDData.defaultSchemeId,
          roughness: 1,
        });

      roleThreeDData.defaultRepresentation.setParameters({
        opacity: 1,
        side: "front",
        smoothSheet: true,
      });

      // loading second time as second component for loop opacity thing
      roleThreeDData.loopComponent = await this.stage.loadFile(
        new Blob([this.proteinData[role].pdb], { type: "text/plain" }),
        {
          ext: "pdb",
        }
      );

      roleThreeDData.loopComponent.setSelection(
        `:${this.proteinData[role].chain}/0`
      );

      roleThreeDData.loopRepresentation =
        roleThreeDData.loopComponent.addRepresentation("cartoon", {
          color: roleThreeDData.defaultSchemeId,
          roughness: 1,
        });

      roleThreeDData.loopRepresentation.setParameters({
        opacity: 1,
        side: "front",
        smoothSheet: true,
        radiusScale: 1.2,
      });

      roleThreeDData.loopComponent.setVisibility(false);

      //for easier identification of hovered protein
      roleThreeDData.structureComponent.structure.role = role;
      roleThreeDData.loopComponent.structure.role = role;
    },

    zoomInOnLoop(role, loop) {
      if (loop == null) {
        this.defaultVisualization(role);
      } else {
        this.threeDData[role].focusedOn = loop;
        this.loopHighlight(role, loop);
        this.loopZoomIn(role, loop);
      }
    },
  },

  mounted: async function () {
    await this.$nextTick();

    this.stage = new Stage("stage", {
      // backgroundColor: "transparent",
      tooltip: false,
    });
    // this.stage.signals.clicked.add(this._click);
    this.stage.signals.hovered.add(this._hover);
    // this.stage.viewer.renderer.domElement.removeAttribute("tabindex"); //don't zoom when scrolling
    this.stage.viewer.renderer.domElement.addEventListener(
      "wheel",
      (e) => e.preventDefault(),
      { passive: false }
    );
    let scaffoldInit = this.initProtein("scaffold");
    let insertInit = this.initProtein("insert");

    await Promise.all([scaffoldInit, insertInit]);

    this.threeDData["insert"].structureComponent.autoView(50);
  },

  computed: {
    ...mapGetters([
      //----
      "getScaffold",
      "getInsert",
      "getSelectedLoop",
      "getZoomedLoop",
      "getCurrentWorkflowStep",
    ]),

    isVisible() {
      return function (role) {
        return this.threeDData[role]?.structureComponent?.visible;
      };
    },

    proteinData() {
      return {
        scaffold: this.getScaffold,
        insert: this.getInsert,
      };
    },
  },

  watch: {
    "getScaffold.acids": {
      handler: function () {
        this.updateSecondaryStructures("scaffold");
      },
      deep: true,
    },

    "getInsert.acids": {
      handler: function () {
        this.updateSecondaryStructures("insert");
      },
      deep: true,
    },

    "getScaffold.selection": {
      handler: function (value) {
        this.highlight("scaffold", value);
      },
      deep: true,
    },

    "getInsert.selection": {
      handler: function (value) {
        this.highlight("insert", value);
      },
      deep: true,
    },

    "getScaffold.zoomedLoop": {
      handler: function (value) {
        this.zoomInOnLoop("scaffold", value);
      },
      deep: true,
    },

    "getInsert.zoomedLoop": {
      handler: function (value) {
        this.zoomInOnLoop("insert", value);
      },
      deep: true,
    },

    getSelectedLoop: {
      handler: function (newLoop, oldLoop) {
        if (this.threeDData[newLoop?.role ?? oldLoop.role].focusedOn != null) {
          return;
        }
        if (newLoop == null) {
          this.defaultVisualization(oldLoop.role);
          return;
        }

        this.loopHighlight(newLoop.role, newLoop.id);
      },
    },
  },
};
</script>

<style>
#stage {
  margin-top: -10px;
  width: 450px;
  height: 400px;
  touch-action: none;
  border-radius: 10px;
}

#stage > canvas {
  background-color: transparent !important;
}
</style>
<style scoped>
.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; */
  border-radius: 10px;
  padding: 4px;
  background: white;
}
.control-panel {
  width: 100%;

  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}

.protein-three-d {
  display: flex;
  justify-content: space-between;
  flex: 1;
  padding-top: 2px;
  padding-bottom: 2px;
  margin: 0px 2px;
  max-width: 48%;
}
</style>