<!-- Copyright (C) 2017 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<template>
  <div id="app">
    <md-app>
      <md-app-toolbar md-tag="md-toolbar" class="top-toolbar">
        <h1 class="md-title" style="flex: 1">{{title}}</h1>
        <md-button
          class="md-primary md-theme-default download-all-btn"
          @click="generateTags()"
          v-if="dataLoaded && canGenerateTags"
        >Generate Tags</md-button>
        <md-button
          class="md-primary md-theme-default"
          @click="downloadAsZip(files)"
          v-if="dataLoaded"
        >Download All</md-button>
        <md-button
          class="md-accent md-raised md-theme-default clear-btn"
          style="box-shadow: none;"
          @click="clear()"
          v-if="dataLoaded"
        >Clear</md-button>
      </md-app-toolbar>

      <md-app-content class="main-content" :style="mainContentStyle">
        <section class="data-inputs" v-if="!dataLoaded">
          <div class="input">
            <dataadb class="adbinput" ref="adb" :store="store"
              @dataReady="onDataReady" @statusChange="setStatus" />
          </div>
          <div class="input" @dragover.prevent @drop.prevent>
            <datainput class="fileinput" ref="input" :store="store"
              @dataReady="onDataReady" @statusChange="setStatus" />
          </div>
        </section>

        <section class="data-view">
          <div
            class="data-view-container"
            v-for="file in dataViewFiles"
            :key="file.type"
          >
            <dataview
              :ref="file.type"
              :store="store"
              :file="file"
              :presentTags="Object.freeze(presentTags)"
              :presentErrors="Object.freeze(presentErrors)"
              :dataViewFiles="dataViewFiles"
              @click="onDataViewFocus(file)"
            />
          </div>

          <overlay
            :presentTags="Object.freeze(presentTags)"
            :presentErrors="Object.freeze(presentErrors)"
            :store="store"
            :ref="overlayRef"
            :searchTypes="searchTypes"
            v-if="dataLoaded"
            v-on:bottom-nav-height-change="handleBottomNavHeightChange"
          />
        </section>
      </md-app-content>
    </md-app>
  </div>
</template>
<script>
import Overlay from './Overlay.vue';
import DataView from './DataView.vue';
import DataInput from './DataInput.vue';
import LocalStore from './localstore.js';
import DataAdb from './DataAdb.vue';
import FileType from './mixins/FileType.js';
import SaveAsZip from './mixins/SaveAsZip';
import FocusedDataViewFinder from './mixins/FocusedDataViewFinder';
import {DIRECTION} from './utils/utils';
import Searchbar from './Searchbar.vue';
import {NAVIGATION_STYLE, SEARCH_TYPE} from './utils/consts';
import {TRACE_TYPES, FILE_TYPES, dataFile} from './decode.js';
import { TaggingEngine } from './flickerlib/common';

const APP_NAME = 'Winscope';

const CONTENT_BOTTOM_PADDING = 25;

export default {
  name: 'app',
  mixins: [FileType, SaveAsZip, FocusedDataViewFinder],
  data() {
    return {
      title: APP_NAME,
      activeDataView: null,
      // eslint-disable-next-line new-cap
      store: LocalStore('app', {
        flattened: false,
        onlyVisible: false,
        simplifyNames: true,
        displayDefaults: true,
        navigationStyle: NAVIGATION_STYLE.GLOBAL,
        flickerTraceView: false,
        showFileTypes: [],
        isInputMode: false,
      }),
      overlayRef: 'overlay',
      mainContentStyle: {
        'padding-bottom': `${CONTENT_BOTTOM_PADDING}px`,
      },
      tagFile: null,
      presentTags: [],
      presentErrors: [],
      searchTypes: [SEARCH_TYPE.TIMESTAMP],
      hasTagOrErrorTraces: false,
    };
  },
  created() {
    window.addEventListener('keydown', this.onKeyDown);
    window.addEventListener('scroll', this.onScroll);
    document.title = this.title;
  },
  destroyed() {
    window.removeEventListener('keydown', this.onKeyDown);
    window.removeEventListener('scroll', this.onScroll);
  },

  methods: {
    /** Get states from either tag files or error files */
    getUpdatedStates(files) {
      var states = [];
      for (const file of files) {
        states.push(...file.data);
      }
      return states;
    },
    /** Get tags from all uploaded tag files*/
    getUpdatedTags() {
      if (this.tagFile === null) return [];
      const tagStates = this.getUpdatedStates([this.tagFile]);
      var tags = [];
      tagStates.forEach(tagState => {
        tagState.tags.forEach(tag => {
          tag.timestamp = Number(tagState.timestamp);
          // tags generated on frontend have transition.name due to kotlin enum
          tag.transition = tag.transition.name ?? tag.transition;
          tags.push(tag);
        });
      });
      return tags;
    },
    /** Get tags from all uploaded error files*/
    getUpdatedErrors() {
      var errorStates = this.getUpdatedStates(this.errorFiles);
      var errors = [];
      //TODO (b/196201487) add check if errors empty
      errorStates.forEach(errorState => {
        errorState.errors.forEach(error => {
          error.timestamp = Number(errorState.timestamp);
          errors.push(error);
        });
      });
      return errors;
    },
    /** Set flicker mode check for if there are tag/error traces uploaded*/
    updateHasTagOrErrorTraces() {
      return this.hasTagTrace() || this.hasErrorTrace();
    },
    hasTagTrace() {
      return this.tagFile !== null;
    },
    hasErrorTrace() {
      return this.errorFiles.length > 0;
    },
    /** Activate flicker search tab if tags/errors uploaded*/
    updateSearchTypes() {
      this.searchTypes = [SEARCH_TYPE.TIMESTAMP];
      if (this.hasTagTrace()) {
        this.searchTypes.push(SEARCH_TYPE.TRANSITIONS);
      }
      if (this.hasErrorTrace()) {
        this.searchTypes.push(SEARCH_TYPE.ERRORS);
      }
    },
    /** Filter data view files by current show settings*/
    updateShowFileTypes() {
      this.store.showFileTypes = this.dataViewFiles
        .filter((file) => file.show)
        .map(file => file.type);
    },
    clear() {
      this.store.showFileTypes = [];
      this.tagFile = null;
      this.$store.commit('clearFiles');
      this.buttonClicked("Clear")
    },
    onDataViewFocus(file) {
      this.$store.commit('setActiveFile', file);
      this.activeDataView = file.type;
    },
    onKeyDown(event) {
      event = event || window.event;
      if (this.store.isInputMode) return false;
      if (event.keyCode == 37 /* left */ ) {
        this.$store.dispatch('advanceTimeline', DIRECTION.BACKWARD);
      } else if (event.keyCode == 39 /* right */ ) {
        this.$store.dispatch('advanceTimeline', DIRECTION.FORWARD);
      } else if (event.keyCode == 38 /* up */ ) {
        this.$refs[this.activeView][0].arrowUp();
      } else if (event.keyCode == 40 /* down */ ) {
        this.$refs[this.activeView][0].arrowDown();
      } else {
        return false;
      }
      event.preventDefault();
      return true;
    },
    onDataReady(files) {
      this.$store.dispatch('setFiles', files);

      this.tagFile = this.tagFiles[0] ?? null;
      this.hasTagOrErrorTraces = this.updateHasTagOrErrorTraces();
      this.presentTags = this.getUpdatedTags();
      this.presentErrors = this.getUpdatedErrors();
      this.updateSearchTypes();
      this.updateFocusedView();
      this.updateShowFileTypes();
    },
    setStatus(status) {
      if (status) {
        this.title = status;
      } else {
        this.title = APP_NAME;
      }
    },
    handleBottomNavHeightChange(newHeight) {
      this.$set(
          this.mainContentStyle,
          'padding-bottom',
          `${ CONTENT_BOTTOM_PADDING + newHeight }px`,
      );
    },
    generateTags() {
      // generate tag file
      this.buttonClicked("Generate Tags");
      const engine = new TaggingEngine(
        this.$store.getters.tagGenerationWmTrace,
        this.$store.getters.tagGenerationSfTrace,
        (text) => { console.log(text) }
      );
      const tagTrace = engine.run();
      const tagFile = this.generateTagFile(tagTrace);

      // update tag trace in set files, update flicker mode
      this.tagFile = tagFile;
      this.hasTagOrErrorTraces = this.updateHasTagOrErrorTraces();
      this.presentTags = this.getUpdatedTags();
      this.presentErrors = this.getUpdatedErrors();
      this.updateSearchTypes();
    },

    generateTagFile(tagTrace) {
      const data = tagTrace.entries;
      const blobUrl = URL.createObjectURL(new Blob([], {type: undefined}));
      return dataFile(
        "GeneratedTagTrace.winscope",
        data.map((x) => x.timestamp),
        data,
        blobUrl,
        FILE_TYPES.TAG_TRACE
      );
    },
  },
  computed: {
    files() {
      return this.$store.getters.sortedFiles.map(file => {
        if (this.hasDataView(file)) {
          file.show = true;
        }
        return file;
      });
    },
    prettyDump() {
      return JSON.stringify(this.dump, null, 2);
    },
    dataLoaded() {
      return this.files.length > 0;
    },
    activeView() {
      if (!this.activeDataView && this.files.length > 0) {
        // eslint-disable-next-line vue/no-side-effects-in-computed-properties
        this.activeDataView = this.files[0].type;
      }
      return this.activeDataView;
    },
    dataViewFiles() {
      return this.files.filter((file) => this.hasDataView(file));
    },
    tagFiles() {
      return this.$store.getters.tagFiles;
    },
    errorFiles() {
      return this.$store.getters.errorFiles;
    },
    timelineFiles() {
      return this.$store.getters.timelineFiles;
    },
    canGenerateTags() {
      const fileTypes = this.dataViewFiles.map((file) => file.type);
      return fileTypes.includes(TRACE_TYPES.WINDOW_MANAGER)
        && fileTypes.includes(TRACE_TYPES.SURFACE_FLINGER);
    },
  },
  watch: {
    title() {
      document.title = this.title;
    },
  },
  components: {
    overlay: Overlay,
    dataview: DataView,
    datainput: DataInput,
    dataadb: DataAdb,
    searchbar: Searchbar,
  },
};
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@600&display=swap');

#app .md-app-container {
  /* Get rid of transforms which prevent fixed position from being used */
  transform: none!important;
  min-height: 100vh;
}

#app .top-toolbar {
  box-shadow: none;
  background-color: #fff;
  background-color: var(--md-theme-default-background, #fff);
  border-bottom: thin solid rgba(0,0,0,.12);
  padding:  0 40px;
}

#app .top-toolbar .md-title {
  font-family: 'Open Sans', sans-serif;
  white-space: nowrap;
  color: #5f6368;
  margin: 0;
  padding: 0;
  font-size: 22px;
  letter-spacing: 0;
  font-weight: 600;
}

.data-view {
  display: flex;
  flex-direction: column;
}

.card-toolbar {
  border-bottom: 1px solid rgba(0, 0, 0, .12);
}

.timeline {
  margin: 16px;
}

.container {
  display: flex;
  flex-wrap: wrap;
}

.md-button {
  margin-top: 1em
}

h1 {
  font-weight: normal;
}

.data-inputs {
  display: flex;
  flex-wrap: wrap;
  height: 100%;
  width: 100%;
  align-self: center;
  /* align-items: center; */
  align-content: center;
  justify-content: center;
}

.data-inputs .input {
  padding: 15px;
  flex: 1 1 0;
  max-width: 840px;
  /* align-self: center; */
}

.data-inputs .input > div {
  height: 100%;
}

.data-view-container {
  padding: 25px 20px 0 20px;
}

.snackbar-break-words {
  /* These are technically the same, but use both */
  overflow-wrap: break-word;
  word-wrap: break-word;
  -ms-word-break: break-all;
  word-break: break-word;
  /* Adds a hyphen where the word breaks, if supported (No Blink) */
  -ms-hyphens: auto;
  -moz-hyphens: auto;
  -webkit-hyphens: auto;
  hyphens: auto;
  padding: 10px 10px 10px 10px;
}
</style>