<template>
  <div class="editor" v-if="ready">
    <tool-bar @click-menu="clickMenu"/>
    <split-panes class="pane-container" @resized="onPanesReSized(0, $event)">
      <split-panes splitpanes-min="40" :splitpanes-size="getSize(0, 0)" horizontal @resized="onPanesReSized(1, $event)">
        <split-panes splitpanes-min="20" :splitpanes-size="getSize(1, 0)" @resized="onPanesReSized(2, $event)">
          <views splitpanes-min="20" :splitpanes-size="getSize(2, 0)"></views>
          <playground splitpanes-min="20" :splitpanes-size="getSize(2, 1)"></playground>
        </split-panes>
        <assets splitpanes-min="25" :splitpanes-size="getSize(1, 1)"></assets>
      </split-panes>
      <inspector splitpanes-min="20" :splitpanes-size="getSize(0, 1)"></inspector>
    </split-panes>
    <bottom-bar/>
    <details-dialog ref="dialogsDialog"/>
    <pack-manager-dialog ref="packManagerDialog" @pack="pack"/>
    <mock-editor-dialog ref="mockEditorDialog"/>
    <code-sync-serve-dialog ref="codeSyncServeDialog"/>
    <missing-packages-dialog ref="missingPackagesDialog"/>
    <px-skin-editor-dialog ref="pxSkinEditorDialog"/>
    <process-search-dialog ref="processSearchDialog"/>
    <behavior-editor-dialog @change="handleBehaviorsChange" ref="behaviorEditorDialog"></behavior-editor-dialog>
    <project-conflict-resolve-dialog ref="projectConflictResolveDialog" @resolved="onConflictResolved"/>
  </div>
</template>

<script>
import {mapState, mapActions, mapMutations, mapGetters} from 'vuex'
import SplitPanes from 'splitpanes'
import ToolBar from "./Editor/ToolBar";
import Inspector from "./Editor/Inspector";
import Views from "./Editor/Views";
import Playground from "./Editor/Playground";
import Assets from "./Editor/Assets";
import DetailsDialog from "./Editor/dialogs/DetailsDialog";
import {clonePureObj, openPreview, playWaiting} from "../utils";
import i18n from "../i18n";
import events from "@/global-events.js"
import db from "../utils/db-storage";
import MockEditorDialog from "./Editor/dialogs/MockEditorDialog";
import {startCodeSyncServe} from "../code-sync-serve";
import BottomBar from "./Editor/BottomBar";
import CodeSyncServeDialog from "./Editor/dialogs/CodeSyncServeDialog";
import MissingPackagesDialog from "./Editor/dialogs/MissingPackagesDialog";
import PackManagerDialog from "./Editor/dialogs/PackManagerDialog";
import PxSkinEditorDialog from "./Editor/dialogs/PxSkinEditorDialog";
import BehaviorEditorDialog from "./Editor/dialogs/BehaviorEditorDialog";
import ProcessSearchDialog from "./Editor/dialogs/ProcessSearchDialog";
import ProjectConflictResolveDialog from "./Editor/dialogs/ProjectConflictResolveDialog";

export default {
  name: 'Editor',
  components: {
    ProjectConflictResolveDialog,
    ProcessSearchDialog,
    BehaviorEditorDialog,
    PxSkinEditorDialog,
    PackManagerDialog,
    MissingPackagesDialog,
    CodeSyncServeDialog,
    BottomBar,
    MockEditorDialog,
    DetailsDialog,
    Assets,
    Playground,
    Views,
    Inspector,
    ToolBar,
    SplitPanes,
  },
  data() {
    const panesConfig = localStorage.panesConfig ? JSON.parse(localStorage.panesConfig) : [0.8, 0.8, 0.3];
    return {
      panesConfig,
      ready: false,
    }
  },
  watch: {
    $route: {
      handler: function (val, oldVal) {
        console.log('router changed');
        this.loadProject();
      },
      deep: true
    }
  },
  computed: {
    ...mapState({
      codeSyncServeConfig(state) {
        return state.editor.codeSyncServeConfig;
      },
      operators: (state) => state.project.operators,
      currentOperator: (state) => state.editor.operator,
    }),
    ...mapGetters(['behaviorEditor'])
  },
  async mounted() {
    if (!window.saveProcesses) {
      window.saveProcesses = (processes) => {
        this.$nextTick(() => {
          this.saveProcesses(processes);
        });
      }
    }
    document.addEventListener('keydown', this.onKeyPress);
    events.$on('save-and-preview', () => {
      this.clickMenu("preview");
    });
    events.$on('show-missing-packages', this.showMissingPackages);
    events.$on('edit-behavior', this.editBehavior);

    events.$on('show-code-sync-serve-dialog', () => {
      this.$refs.codeSyncServeDialog.show();
    });

    await playWaiting(this.prepare(), this.$t('Preparing')).catch(e => {
      console.log(e);
    });
    if (this.codeSyncServeConfig.autoLaunch) {
      setTimeout(() => {
        startCodeSyncServe(this.codeSyncServeConfig);
      }, 100);
    }

    this.loadProject().catch(e => {
      console.log(e);
    });
  },
  destroyed() {
    document.removeEventListener('keydown', this.onKeyPress);
    events.$off('show-missing-packages', this.showMissingPackages);
    events.$off('edit-behavior', this.editBehavior);
  },

  methods: {
    prepare() {
      return Promise.all([
        db.open('store'),
        this.updateEnv(),
      ])
    },
    onKeyPress(e) {
      if (e.key === 's' && (e.ctrlKey || e.metaKey)) {
        e.preventDefault();

        this.saveProject();

        return false;
      }
    },
    async loadProject(forceRemote) {
      const {projectID, project, dataUrl} = this.$route.params;
      if (!forceRemote && await this.localVersionExist(projectID)) {
        /*this.$confirm(this.$t('Unsaved version found locally, choose one'), this.$t('Alert'), {
          showClose: false,
          closeOnClickModal: false,
          closeOnPressEscape: false,
          confirmButtonText: this.$t('Local Version'),
          cancelButtonText: this.$t('Remote Version'),
          type: 'warning',
        }).then(() => {
          this.loadLocalVersion(projectID);
        }).catch((e) => {
          if (e === 'cancel') {
            this.loadRemoteVersion(projectID, project, dataUrl);
          } else {
            console.log(e);
          }
        });*/
        this.loadLocalVersion(projectID);
      } else {
        this.loadRemoteVersion(projectID, project, dataUrl);
      }
    },
    loadLocalVersion(projectID) {
      this.ready = false;
      this.loadFromLocal(projectID);
      this.$nextTick(() => {
        this.ready = true;
      });
    },
    async loadRemoteVersion(projectID, project, dataUrl, projectData) {
      if (projectID) {
        this.ready = false;
        let p = projectData ? (this.loadFromData({
          projectID,
          projectData
        })) : dataUrl ? this.loadFromDataUrl({projectID, dataUrl}) : this.loadFromRemote({projectID});
        await playWaiting(p, this.$t('Preparing')).catch(e => {
          this.$alert(this.$t('Project does not exist'), this.$t('Alert'), {
            confirmButtonText: this.$t('Confirm'),
            callback: action => {
              this.$router.replace({name: 'home'});
            }
          });
        });
        this.ready = true;
      } else {
        this.$router.push({name: 'home'})
      }
    },
    getSize(id, side) {
      let ratio = this.panesConfig[id];
      return (side === 0 ? ratio : 1 - ratio) * 100
    },
    onPanesReSized(id, configs) {
      this.panesConfig[id] = configs[0].width / 100;
      localStorage.panesConfig = JSON.stringify(this.panesConfig);
    },
    async saveProject(closeLoading, data) {

      let remark, cancel;
      await this.$prompt(this.$t('Input version remark'), this.$t('Alert'), {
        confirmButtonText: this.$t('Confirm'),
        showClose: false,
        inputPattern: /^.{1,256}$/,
      }).then(
          ({value}) => {
            remark = this.lastSaveRemark = value;
          }
      ).catch((action) => {
        cancel = true;
      });
      if (!cancel) {
        console.log("保存项目", data, remark)
        let resp = await playWaiting(this.saveToRemote({remark, data}), this.$t('Saving'), closeLoading);
        if (resp.result) {
          this.$message({
            message: i18n.t('Save project successfully'), //因为message是异步出现，但是当路由回退的时候，this.i18n的实例已经置空，所以要用全局的i18n实例
            type: 'success'
          });
        } else {
          cancel = true;
          await this.$confirm(i18n.t('There are conflicts in the project'), i18n.t('Alert'), {
            confirmButtonText: i18n.t('Confirm'),
            cancelButtonText: i18n.t('Cancel'),
            type: 'warning'
          }).then(() => {

            this.$refs.projectConflictResolveDialog.show(resp.remoteData, resp.localData);
          }).catch((e) => {
            this.$loading({}).close();
          });
        }
      }

      return cancel;
    },
    async onConflictResolved(data) {
      const {projectID, project} = this.$route.params;
      let cancel = await this.saveProject(true, data);
      if (!cancel) {
        this.loadRemoteVersion(projectID, project, undefined, data);
      }
    },
    checkAuth() {
      let checkStatus = this.operators.includes(this.currentOperator);
      if (!checkStatus) {
        this.$message({
          message: this.$t('No permission'),
          type: 'error',
          duration: 1000,
        });
      }
      return checkStatus;
    },
    async clickMenu(menuItem) {
      switch (menuItem) {
        case 'save':
          try {
            this.checkAuth() && this.saveProject();
          } catch (e) {
          }
          break;
        case 'drop-local-modification':
          await this.$confirm(i18n.t('Are you sure to discard the local modification'), i18n.t('Alert'), {
            confirmButtonText: i18n.t('Confirm'),
            cancelButtonText: i18n.t('Cancel'),
            type: 'warning'
          }).then(() => {
            this.loadProject(true);
          }).catch((e) => {
          });
          break;
        case 'details':
          this.$refs.dialogsDialog.show();
          break;
          /*case 'preview':
            this.preview();
            await this.pack({debug: true});
            break;*/
        case 'preview-fast':
          this.preview();
          break;
        case 'pack':
          this.checkAuth() && await this.pack({});
          break;
        case 'pack-mix-mode':
          this.checkAuth() && await this.pack({mix: true});
          break;
        case 'pack-debug-mode':
          this.checkAuth() && await this.pack({debug: true});
          break;
        case 'pack-manager':
          this.checkAuth() && this.$refs.packManagerDialog.show();
          break;
        case 'search':
          await this.$refs.processSearchDialog.show();
          break;
        case 'mock':
          this.$refs.mockEditorDialog.show();
          break;
        case 'undo':
          this.$store.commit('undoRedo', 1);
          break;
        case 'redo':
          this.$store.commit('undoRedo', -1);
          break;
        case 'px-skin-editor':
          this.checkAuth() && this.$refs.pxSkinEditorDialog.show();
          break;
        case 'exit':
          const {projectID} = this.$route.params;
          if (await this.localVersionExist(projectID)) {
            this.$confirm(this.$t('Unsaved Alert'), this.$t('Alert'), {
              confirmButtonText: this.$t('Save'),
              cancelButtonText: this.$t('Exit'),
              type: 'warning'
            }).then(async () => {
              try {
                await this.saveProject();
                this.backToHome();
              } catch (e) {
              }
            }).catch((e) => {
              this.backToHome();
            });
          } else {
            this.backToHome();
          }
          break;
      }
    },
    async pack({debug, mix}) {
      let cancel;
      cancel = await this.saveProject(false);
      if (cancel) {
        return;
      }
      const loading = this.$loading({
        lock: true,
        text: this.$t('Packing'),
      });
      try {
        await this.packProject({
          mix: mix || false,
          debug: debug || false,
          remark: this.lastSaveRemark,
        });
        this.$message({
          message: this.$t('Pack project successfully'),
          type: 'success',
          duration: 500,
        });

        this.$refs.packManagerDialog.show();
      } catch (e) {
        console.log(e);
        this.$message({
          message: this.$t('Pack project failed'),
          type: 'error',
          duration: 1000,
        });
        if (e.details) {
          let details = e.details.split('\n').join('\n');
          this.$alert(`<p>Compile error</p><pre style="color:red;">${details}</pre>`, this.$t('Alert'), {
            dangerouslyUseHTMLString: true,
            customClass: 'pack-failed-details'
          });
        }
      }
      loading.close();
    },
    backToHome() {
      this.$router.replace({name: 'home'});
    },
    preview() {
      this.savePreview();

      const {projectID} = this.$route.params;

      let previewUrl = new URL(location.href);
      previewUrl.hash = '#/preview/' + projectID;

      setTimeout(() => {
        if (this.previewWindow && this.previewWindow.reloadPreview && this.previewWindow.reloadPreview()) {
          this.previewWindow.reloadPreview();
        } else {
          this.previewWindow = open(previewUrl.href, 'blank');
        }
      }, 300);
    },
    showMissingPackages(missingPackages) {
      this.$refs.missingPackagesDialog.show(missingPackages);
    },
    editBehavior(path, index) {
      switch (this.behaviorEditor) {
        case 'builtin':
          this.$refs.behaviorEditorDialog.show(path, index);
          break;
        case 'standalone':
          this.onOpenStandalone(path, index);
          break;
      }
    },
    onOpenStandalone(path, index) {
      let processEditorUrl = location.href.substr(0, location.href.indexOf('#')) + '#/process-editor';
      let processEditorWin = this.processEditorWin = open(processEditorUrl, 'Process Editor', 'width=800, height=600');

      this.updateProcessEditorWin(processEditorWin);

      if (processEditorWin.editable) {
        if (processEditorWin.editable()) {
          this.showStandaloneEditor(path, index);
        } else {
          this.$message({
            message: this.$t('A behavior is being edited. Please save it first'),
            type: 'warning'
          });
        }
      } else {
        processEditorWin.onload = () => {
          this.showStandaloneEditor(path, index);
        }
      }
    },
    showStandaloneEditor(path, index) {
      let processEditorWin = this.processEditorWin;

      let projectData = clonePureObj(this.$store.state.project.data);
      let packageState = clonePureObj(this.$store.state.project.package);
      processEditorWin.reset({projectData, packageState});
      processEditorWin.edit(path, index);
    },
    saveProcesses(processes) {
      this.overwriteProcesses({processes});
      this.$message({
        message: this.$t('Save behavior successfully'),
        type: 'success'
      });
    },
    handleBehaviorsChange() {
      this.makeProjectDirty();
    },
    ...mapMutations(['makeProjectDirty', 'overwriteProcesses', 'updateProcessEditorWin']),
    ...mapActions([
      'loadPackages',
      'localVersionExist',
      'loadFromLocal',
      'loadFromRemote',
      'loadFromDataUrl',
      'loadFromData',
      "saveToLocal",
      "saveToRemote",
      'updateEnv',
      'packProject',
      'savePreview',
      'packImages',
    ])
  }
}
</script>

<style lang="scss">
</style>
