<template>
  <div class="jsk-code-editor-container">
    <jsk-tabs
      ref="tabs"
      class="file-tabs"
      :tabs-init-active="0"
      :tabs-theme="settings.theme || 'light'"
      tabs-height="35px"
      :has-tooltip="hasTabTooltips"
      :is-closable="areTabClosable"
      :has-options="hasControlPanel"
      :tabs-options-width="controlWidth"
    >
      <jsk-tab
        v-for="(file, index) in files"
        :key="index"
        :tab-index="index"
        :tab-tooltip="tooltip(file)"
        has-mark
        :before-close="beforeCloseTab(index)"
        :is-closable="isFileClosable(file, index)"
        @switch="switchToTab"
        @close="closeTab"
        @contextmenu.native.prevent="$emit('context-menu', index)"
      >
        <div
          v-if="file.isSaving"
          slot="mark"
          class="saving-mark"
        >
          <i class="el-icon-j-loading" />
        </div>
        <div
          v-else-if="file.isModified"
          slot="mark"
          class="modified-mark"
        >
          ●
        </div>
        {{ file.name }}
      </jsk-tab>
      <ul
        slot="options"
        class="jsk-code-editor-control"
      >
        <slot name="controls" />
        <li v-if="hasNewFileControl">
          <i
            class="el-icon-j-plus"
            @click="createAndSwitchToNewFile"
            @touchstart.prevent="createAndSwitchToNewFile"
          />
        </li>
        <li v-if="hasSaveCurrentFileControl">
          <i
            class="el-icon-j-save"
            @click="saveCurrentFile"
            @touchstart.prevent="saveCurrentFile"
          />
        </li>
        <li v-if="hasSettingsControl">
          <jsk-popover
            popover-placement="bottom-end"
            popover-width="240"
            popover-trigger="click"
            popover-offset="40"
          >
            <jsk-form
              ref="form"
              form-size="small"
              form-label-position="top"
              class="jsk-settings-form"
              :form-model="settings"
              :form-label-width="120"
            >
              <jsk-form-item
                v-for="(label, key) in settingsContent.labels"
                :key="key"
                :form-item-label="label"
              >
                <jsk-radio-group
                  v-model="settings[key]"
                  radio-group-size="mini"
                >
                  <jsk-radio-button
                    v-for="(content, labelName) in settingsContent[key]"
                    :key="labelName"
                    :label="labelName"
                  >
                    {{ content }}
                  </jsk-radio-button>
                </jsk-radio-group>
              </jsk-form-item>
            </jsk-form>
            <i
              slot="reference"
              class="el-icon-j-gear"
            />
          </jsk-popover>
        </li>
      </ul>
    </jsk-tabs>
    <div class="jsk-code-editor">
      <codemirror
        ref="codemirror"
        :options="editorOptions"
        class="jsk-code-editor-codemirror"
        :style="{
          fontSize: editorFontSize,
          lineHeight: '1.5em',
        }"
        @input="setCurrentFileModificationState"
        @refresh="renderCurrentFileLineArrows"
        @gutterContextMenu="setBreakpoint"
      />
      <code-editor-context-menu
        v-show="contextMenuVisible"
        :style="contextMenuPosition"
        :menu-mode="contextMenuMode"
        :has-lock-control="hasLockControl"
        :has-placeholder-control="hasPlaceholderControl"
        @lock-change="setCurrentFileModificationState"
        @lock="lockSelectedCode"
        @unlock="unlockLockedCode"
        @hide="lockSelectedCode(true)"
        @add-placeholder="addPlaceholder"
        @remove-placeholder="removePlaceholder"
      />
      <code-editor-feedback-tooltip
        :feedback-tooltip-placement="feedbackTooltipPlacement"
        :feedback-tooltip-position="feedbackTooltipPosition"
        :feedback-tooltip-visible="feedbackTooltipVisible"
        :feedback-tooltip-theme="feedbackTooltipTheme"
        :feedback-tooltip-content="feedbackTooltipContent"
        feedback-tooltip-max-width="25vw"
      />
    </div>
    <code-editor-close-confirm
      :close-confirm-filename="closeConfirmFileName"
      :close-confirm-content="closeConfirmContent"
      :visible.sync="closeConfirmVisible"
    />
    <code-editor-reload-confirm
      :reload-confirm-filename="reloadConfirmFileName"
      :reload-confirm-content="reloadConfirmContent"
      :visible.sync="reloadConfirmVisible"
    />
    <code-editor-lock-confirm
      :lock-confirm-content="lockConfirmContent"
      :lock-confirm-positions="lockConfirmPositions"
      :visible.sync="lockConfirmVisible"
    />
  </div>
</template>

<script>
import JskTab from '../tab/Tab';
import JskTabs from '../tabs/Tabs';
import JskForm from '../form/Form';
import JskPopover from '../popover/Popover';
import JskFormItem from '../form-item/FormItem';
import JskRadioButton from '../radio-button/RadioButton';
import JskRadioGroup from '../radio-group/RadioGroup';
import { codemirror } from 'vue-codemirror';
import 'codemirror/lib/codemirror.css';
import '../../plugins/codemirror';
import CodeEditorCloseConfirm from './addons/CloseConfirm';
import CodeEditorReloadConfirm from './addons/ReloadConfirm';
import CodeEditorLockConfirm from './addons/LockConfirm';
import CodeEditorContextMenu from './addons/ContextMenu';
import CodeEditorFeedbackTooltip from './addons/FeedbackTooltip';
import CodeEditorCodemirrorOptions from './configs/options';
import CodeEditorDefaultSettings from './configs/settings';
import breakpoints from './mixins/breakpoints';
import files from './mixins/files';
import feedbacks from './mixins/feedbacks';
import settings from './mixins/settings';
import marks from './mixins/marks';
const _isEqual = require('lodash.isequal');
const _merge = require('lodash.merge');
const _cloneDeep = require('lodash.clonedeep');
export default {
  name: 'JskCodeEditor',
  components: {
    CodeEditorCloseConfirm,
    CodeEditorReloadConfirm,
    CodeEditorLockConfirm,
    CodeEditorContextMenu,
    CodeEditorFeedbackTooltip,
    codemirror,
    JskTabs,
    JskTab,
    JskForm,
    JskPopover,
    JskFormItem,
    JskRadioButton,
    JskRadioGroup
  },
  mixins: [
    breakpoints,
    feedbacks,
    files,
    settings,
    marks
  ],
  props: {
    codeEditorCloseConfirmContent: Object,
    codeEditorReloadConfirmContent: Object,
    codeEditorLockConfirmContent: Object,
    codeEditorSettingsContent: Object,
    codeEditorSettings: Object,
    codeEditorFiles: {
      type: Array,
      default: () => {
        return [];
      }
    },
    codeEditorLanguage: {
      type: String,
      default: 'en'
    },
    codeEditorOptions: {
      type: Object,
      default: () => {
        return {};
      }
    },
    codeEditorInitActive: {
      type: Number,
      default: 0
    },
    codeEditorControlPanelWidth: {
      type: [String, Number],
      default: 100
    },
    areTabClosable: {
      type: Boolean,
      default: true
    },
    isViewportKept: {
      type: Boolean,
      default: false
    },
    hasRepeatWordsAnnotations: {
      type: Boolean,
      default: false
    },
    hasLineNotes: {
      type: Boolean,
      default: true
    },
    hasFeedbackNotes: {
      type: Boolean,
      default: true
    },
    hasLineArrows: {
      type: Boolean,
      default: true
    },
    hasModificationLossConfirm: {
      type: Boolean,
      default: false
    },
    hasLockControl: {
      type: Boolean,
      default: false
    },
    hasBreakpointControl: {
      type: Boolean,
      default: false
    },
    hasControlPanel: {
      type: Boolean,
      default: true
    },
    hasNewFileControl: {
      type: Boolean,
      default: true
    },
    hasSaveCurrentFileControl: {
      type: Boolean,
      default: true
    },
    hasSettingsControl: {
      type: Boolean,
      default: true
    },
    hasTabTooltips: {
      type: Boolean,
      default: true
    },
    hasCustomTabTooltip: {
      type: Boolean,
      default: false
    },
    customTabTooltips: {
      type: Array,
      default: () => {
        return []
      }
    },
    beforeSaveFile: {
      type: Function,
      default: () => {
        return Promise.resolve(true);
      }
    },
    beforeSaveAllFiles: {
      type: Function,
      default: () => {
        return Promise.resolve(true);
      }
    },
    canCreateNewFile: {
      type: Boolean,
      default: true
    },
    isFirstFileClosable: {
      type: Boolean,
      default: true
    },
    hasPlaceholderControl: {
      type: Boolean,
      default: false
    }
  },
  data: function() {
    return {
      closeConfirmVisible: false,
      closeConfirmFileName: undefined,
      reloadConfirmVisible: false,
      reloadConfirmFileName: undefined,
      lockConfirmVisible: false,
      lockConfirmPositions: [],
      feedbackTooltipPlacement: undefined,
      feedbackTooltipVisible: false,
      feedbackTooltipContent: undefined,
      feedbackTooltipPosition: { left: 0, top: 0 },
      contextMenuVisible: false,
      contextMenuMode: 'lock',
      contextMenuPosition: { left: 0, top: 0 },
      selectedLocks: undefined,
      selectedPlaceholders: undefined,
      settings: _cloneDeep(CodeEditorDefaultSettings),
      editorOptions: _cloneDeep(CodeEditorCodemirrorOptions),
      editorFontSizeOption: 'large',
      currentActiveIndex: 0,
      hasPlaceholderFocused: false
    };
  },
  computed: {
    codemirror() {
      return this.$refs.codemirror.codemirror;
    },
    controlWidth() {
      if (typeof this.codeEditorControlPanelWidth === 'string') {
        return this.codeEditorControlPanelWidth;
      }
      return this.codeEditorControlPanelWidth + 'px';
    }
  },
  watch: {
    files: function () {
      let viewport = null;
      if (this.isViewportKept) {
        viewport = this.codemirror.getScrollInfo();
      }
      this.initFiles();
      if (this.isViewportKept) {
        this.$nextTick(() => {
          this.codemirror.scrollTo(viewport.left, viewport.top);
        });
      }
    },
    settings: {
      deep: true,
      handler: function() {
        this.editorOptions.theme = this.editorTheme;
        this.setEditorIndentSize(this.settings.indent);
        this.setEditorMode(this.settings.mode);
        this.setEditorFontSize(this.settings.fontSize);
        this.setEditorAutoIndent(this.settings.autoIndent);
        if (!_isEqual(this.settings, this.codeEditorSettings)) {
          this.$emit('update:codeEditorSettings', this.settings);
        }
      }
    },
    editorFontSizeOption: function () {
      this.$nextTick(() => {
        this.renderCurrentFile();
      })
    },
    currentFileCode: function() {
      this.reloadCurrentFile();
    },
    currentFileLocks: {
      deep: true,
      handler: function() {
        this.reloadCurrentFile();
      }
    },
    currentFileReadOnly: function() {
      this.reloadCurrentFile();
    },
    hasLockControl() {
      this.updateControlMenuListeners();
    },
    hasPlaceholderControl() {
      this.updateControlMenuListeners();
    },
    hasPlaceholderFocused(newValue) {
      if (this.$refs.codemirror) {
        const codemirrorWrapper = this.$refs.codemirror.cminstance.getWrapperElement();
        if (newValue) {
          codemirrorWrapper.classList.add('has-placeholder-focused');
        } else {
          codemirrorWrapper.classList.remove('has-placeholder-focused');
        }
      }
    }
  },
  created: function() {
    this.initEditorOptions();
  },
  mounted: function() {
    this.initFiles();
    this.initEditorListeners();
    this.setActiveTab(this.codeEditorInitActive);
    this.switchToCurrentFile();
  },
  beforeDestroy: function() {
    if (this.hasModificationLossConfirm) {
      window.removeEventListener('beforeunload', this.modificationLossPrevention)
    }
  },
  methods: {
    initEditorOptions() {
      this.editorOptions = _merge(this.editorOptions, this.codeEditorOptions);
      if (this.codeEditorSettings !== undefined) {
        this.settings = this.codeEditorSettings;
      } else {
        this.settings = CodeEditorDefaultSettings;
      }
      this.editorOptions.extraKeys['Ctrl-S'] = () => this.saveCurrentFile();
      this.editorOptions.extraKeys['Cmd-S'] = () => this.saveCurrentFile();
      if (this.hasRepeatWordsAnnotations) {
        this.editorOptions.highlightSelectionMatches = {
          trim: true,
          delay: 100,
          annotateScrollbar: true
        };
      }
    },
    initEditorListeners() {
      let cm = this.$refs.codemirror.cminstance;
      cm.save = this.saveCurrentFile;
      this.updateControlMenuListeners(cm);
      this.initFeedbackTooltipListener(cm);
      if (this.hasModificationLossConfirm) {
        window.addEventListener('beforeunload', this.modificationLossPrevention)
      }
    }
  }
};
</script>
<style lang="scss" src="../../theme/code-editor.scss"></style>

<style scoped>
.file-tabs {
  flex-shrink: 0;
  flex-grow: 0;
}
.modified-mark,
.saving-mark {
  color: #399af4;
}
.saving-mark {
  padding-top: 1px;
}
</style>
