<template>
  <v-container fluid>

    <v-overlay :value="loading && !configuration" absolute>
      <v-progress-circular
        indeterminate
        size="64"
      ></v-progress-circular>
    </v-overlay>

    <v-overlay :value="!configuration && !loading" absolute opacity="0.8">
      <v-row justify="center">
        <v-alert
          type="error"
        >
          The configuration could not be loaded.
        </v-alert>
      </v-row>
      <v-row justify="center">
        <v-btn color="primary" @click="getConfiguration">Retry</v-btn>
      </v-row>
    </v-overlay>

    <v-window v-model="viewMode">
      <v-window-item>
    <v-row>
      <v-col cols="4" max-height="100%">
        <v-toolbar
          dense
          flat
          @contextmenu="showContextMenu"
        >
          <v-toolbar-title>
            Survey configuration
          </v-toolbar-title>
          <v-spacer/>
          <template v-if="dirty">
            <v-icon left color="primary" @click="saveToFile" title="Save changes to file">mdi-content-save-outline</v-icon>
            <v-icon color="primary" @click="upload" title="Upload changes to server">mdi-cloud-upload</v-icon>
          </template>
        </v-toolbar>

        <v-switch
          dense
          label="Active"
          title="Inactive surveys do not take new data, but changes to the logs can still be made."
          :disabled="!configuration"
          v-model="surveyState"
        ></v-switch>

        <v-treeview
          dense
          activatable
          hoverable
          :multiple-active="false"
          :active.sync="active"
          :open.sync="open"
          :items="treeview"
          style="cursor:pointer;"
        >
        </v-treeview>

      </v-col>

      <v-col cols="8" v-if="activeComponent">
        <component
          :is="activeComponent"
          v-bind="activeValues"
          v-model.sync="configuration"
          @update="activeUpdateHandler"
          @close="deselect"
        ></component>
      </v-col>
      <v-col cols="8" v-else>
        <v-card>
          <v-card-text>
            <p>Select a configuration section to change its settings. When clicking <v-btn small color="primary">Save</v-btn> the changes are immediately saved to the server and start to take effect.</p>

            <v-divider class="my-5"></v-divider>

            <v-alert border="left" type="warning">
              Be careful when changing configuration settings! It is rather easy to break things.
            </v-alert>

            <v-alert border="left" type="info">
              On the first save, the survey will be switched to <em>inactive</em> if not already so. This means that no new data will be read. Remember to switch back to <strong>active</strong> when satisfied with your changes.
            </v-alert>

            <v-divider class="my-5"></v-divider>

            <p>It is recommended that you download a backup of your configuration before making any changes.</p>

            <v-divider class="my-5"></v-divider>

          </v-card-text>
          <v-card-actions>
            <v-btn
              outlined
              color="primary"
              title="Fetch the configuration from the server anew. Any unsaved changes you might have will be lost."
              @click="getConfiguration"
            >
              <v-icon small left>mdi-cloud-refresh</v-icon>
              Refresh
            </v-btn>

            <v-spacer></v-spacer>

            <v-btn
              outlined
              class="ml-2"
              color="primary"
              :disabled="!configuration"
              @click="saveToFile"
              title="Save the current configuration to a file, including any changes you might have made but not yet sent to the server."
            >
              <v-icon small left>mdi-content-save-outline</v-icon>
              Save
            </v-btn>

            <v-dialog
              max-width="400px"
              v-model.sync="fileLoadDialog"
            >
              <template v-slot:activator="{ on, attrs }">
                <v-btn
                  outlined
                  class="ml-2"
                  color="primary lighten-2"
                  title="Load the configuration from a file. It will overwrite any changes that you might have made so far but it won't upload the configuration to the server. You can review the configuration and make changes before using the upload button."
                  v-bind="attrs"
                  v-on="on"
                >
                  <v-icon small left>mdi-folder-open-outline</v-icon>
                  Load
                </v-btn>
              </template>
              <v-card flat>
                <v-card-text class="pt-5">
                  <v-file-input
                    v-model="files"
                    class="mt-4"
                    show-size
                    accept="application/json,application/yaml,.json,.yaml"
                    label="Select configuration file"
                    append-outer-icon="mdi-folder-open-outline"
                    :error-messages="fileInputErrors"
                    @click:append-outer="loadFromFile"
                  ></v-file-input>
                </v-card-text>
              </v-card>
            </v-dialog>

            <v-spacer></v-spacer>

            <v-btn
              class="ml-2"
              color="warning"
              :disabled="!configuration"
              title="Save the configuration on the server. This will replace the existing configuration. The project will be set to INACTIVE. Change its state back to active once you're satisfied with your changes."
              @click="upload"
            >
              <v-icon small left>mdi-cloud-upload</v-icon>
              Upload
            </v-btn>
          </v-card-actions>
        </v-card>
      </v-col>
    </v-row>
      </v-window-item>

      <v-window-item>
        <v-row>
          <v-col cols="12">
            <v-toolbar
              dense
              flat
              @contextmenu="showContextMenu"
            >
              <v-toolbar-title>
                Advanced survey configuration
              </v-toolbar-title>
              <v-spacer/>
              <v-btn small outlined @click="viewMode=0">Go to normal configuration</v-btn>
            </v-toolbar>
          </v-col>
        </v-row>
        <v-row>
          <v-col cols="12">
            <dougal-json-builder
              name="Dougal configuration"
              v-model="configuration"
            ></dougal-json-builder>
          </v-col>
        </v-row>
      </v-window-item>

    </v-window>

    <v-menu
      v-model="contextMenu"
      :position-x="contextMenuX"
      :position-y="contextMenuY"
      absolute
      offset-y
    >
      <v-list dense>
        <v-list-item>
          <v-btn
            small
            outlined
            color="red"
            title="Not a good idea"
            @click="viewMode=1"
          >Advanced configuration…</v-btn>
        </v-list-item>
      </v-list>
    </v-menu>
  </v-container>
</template>


<script>
import YAML from 'yaml';
import { mapActions, mapGetters } from 'vuex';
import { deepSet } from '@/lib/utils';

import DougalJsonBuilder from '@/components/json-builder/json-builder';

import DougalProjectSettingsNameId from '@/components/project-settings/name-id';
import DougalProjectSettingsGroups from '@/components/project-settings/groups';
import DougalProjectSettingsGeodetics from '@/components/project-settings/geodetics';
import DougalProjectSettingsBinning from '@/components/project-settings/binning';
import DougalProjectSettingsFilePath from '@/components/project-settings/file-path';
import DougalProjectSettingsPreplots from '@/components/project-settings/preplots';
import DougalProjectSettingsRawP111 from '@/components/project-settings/input-raw-p111';
import DougalProjectSettingsFinalP111 from '@/components/project-settings/input-final-p111';
import DougalProjectSettingsRawNTBP from '@/components/project-settings/input-raw-ntbp';
import DougalProjectSettingsFinalPending from '@/components/project-settings/input-final-pending';
import DougalProjectSettingsSmartsourceHeader from '@/components/project-settings/input-smartsource-header';
import DougalProjectSettingsSmartsourceSegy from '@/components/project-settings/input-smartsource-segy';
import DougalProjectSettingsPlanner from '@/components/project-settings/planner';
import DougalProjectSettingsOnlineLineNameFormat from '@/components/project-settings/online-line-name-format';
import DougalProjectSettingsASAQC from '@/components/project-settings/asaqc';
import DougalProjectSettingsProduction from '@/components/project-settings/production';
// Temporary placeholder component
import DougalProjectSettingsNotImplemented from '@/components/project-settings/not-implemented';

const components = {
  name_id:  DougalProjectSettingsNameId,
  groups: DougalProjectSettingsGroups,
  geodetics: DougalProjectSettingsGeodetics,
  binning: DougalProjectSettingsBinning,
  input_files:  DougalProjectSettingsFilePath,
  preplots:  DougalProjectSettingsPreplots,
  //raw_data: DougalProjectSettingsNotImplemented,
  raw_data_p111: DougalProjectSettingsRawP111,
  raw_data_smsrc_header: DougalProjectSettingsSmartsourceHeader,
  raw_data_smsrc_segy: DougalProjectSettingsSmartsourceSegy,
  raw_data_ntbp: DougalProjectSettingsRawNTBP,
  //final_data: DougalProjectSettingsNotImplemented,
  final_data_p111: DougalProjectSettingsFinalP111,
  final_data_pending: DougalProjectSettingsFinalPending,
  line_name_format: DougalProjectSettingsOnlineLineNameFormat,
  planner_settings: DougalProjectSettingsPlanner,
  logging: DougalProjectSettingsNotImplemented,
  logging_preset_comments: DougalProjectSettingsNotImplemented,
  logging_labels: DougalProjectSettingsNotImplemented,
  asaqc: DougalProjectSettingsASAQC,
  production: DougalProjectSettingsProduction
}

export default {
  name: "DougalProjectSettings",

  components: {
    DougalJsonBuilder
  },

  data () {
    return {
      configuration: null,
      settings: null,
      active: [],
      open: [],
      files: [],
      treeview: [
      /*
        {
          id: 0,
          name: "Archive",
          values: (cfg) => cfg.archived,
          save: (data, cfg) => {
            cfg.archived = data.archived;
            return cfg;
          }
        },
      */
        {
          id: "name_id",
          name: "Name and ID",
          values: (obj) => ({
            id: obj?.id,
            name: obj?.name
          })
        },
        {
          id: "groups",
          name: "Groups",
          values: (obj) => ({
            groups: obj?.groups
          })
        },
        {
          id: "geodetics",
          name: "Geodetics",
          values: (obj) => ({
            epsg: obj?.epsg
          })
        },
        {
          id: "binning",
          name: "Binning",
          values: (obj) => ({
            ...obj.binning
          })
        },
        {
          id: "input_files",
          name: "Input files",
          values: obj => ({ rootPath: obj.rootPath}),
          children: [
            {
              id: "preplots",
              name: "Preplots",
              values: (obj) => ({
                preplots: structuredClone(obj.preplots),
                rootPath: obj.rootPath
              })
            },
            {
              id: "raw_data",
              name: "Raw data",
              children: [
                {
                  id: "raw_data_p111",
                  name: "P1/11",
                  values: (obj) => ({
                    rootPath: obj.rootPath,
                    globs: obj.raw.p111.globs,
                    paths: obj.raw.p111.paths,
                    pattern: obj.raw?.p111?.pattern,
                    lineNameInfo: obj.raw?.p111?.lineNameInfo
                  })
                },
                {
                  id: "raw_data_smsrc",
                  name: "Smartsource",
                  children: [
                    {
                      id: "raw_data_smsrc_header",
                      name: "Headers",
                      values: (obj) => ({
                        rootPath: obj.rootPath,
                        globs: obj?.raw?.source?.smsrc?.header?.globs,
                        paths: obj?.raw?.source?.smsrc?.header?.paths,
                        pattern: obj?.raw?.source?.smsrc?.header?.pattern,
                        lineNameInfo: obj?.raw?.source?.smsrc?.header?.lineNameInfo
                      })
                    },
                    {
                      id: "raw_data_smsrc_segy",
                      name: "Hydrophone data",
                      values: (obj) => ({
                        rootPath: obj.rootPath,
                        globs: obj?.raw?.source?.smsrc?.segy?.globs,
                        paths: obj?.raw?.source?.smsrc?.segy?.paths,
                        pattern: obj?.raw?.source?.smsrc?.segy?.pattern,
                        lineNameInfo: obj?.raw?.source?.smsrc?.segy?.lineNameInfo
                      })
                    }
                  ]
                },
                {
                  id: "raw_data_ntbp",
                  name: "NTBP detection",
                  values: (obj) => ({
                    regex: obj.raw.ntbp?.pattern?.regex,
                    flags: obj.raw.ntbp?.pattern?.flags
                  })
                }
              ]
            },
            {
              id: "final_data",
              name: "Final data",
              children: [
                {
                  id: "final_data_p111",
                  name: "P1/11",
                  values: (obj) => ({
                    rootPath: obj.rootPath,
                    globs: obj.final.p111.globs,
                    paths: obj.final.p111.paths,
                    pattern: obj.final.p111.pattern,
                    lineNameInfo: obj.final?.p111?.lineNameInfo
                  })
                },
                {
                  id: "final_data_pending",
                  name: "Pending line detection",
                  values: (obj) => ({
                    regex: obj.final.pending?.pattern?.regex,
                    flags: obj.final.pending?.pattern?.flags
                  })
                }
              ]
            },
          ]
        },
        {
          id: "line_name_format",
          name: "Line name format",
          values: (obj) => ({
            fields: this.makeSection(obj, "online.line.lineNameBuilder.fields", []),
            values: this.makeSection(obj, "online.line.lineNameBuilder.values",
              Object.fromEntries(
                Object.keys(this.settings.lineNameBuilder.properties ?? {}).map( k => [k, undefined] ))),
            properties: this.settings.lineNameBuilder.properties ?? {}
          }),
          update: (obj) => {
            const configuration = structuredClone(this.configuration);
            deepSet(configuration, ["online", "line", "lineNameBuilder"], obj);
            this.configuration = configuration;
          }
        },
        {
          id: "planner_settings",
          name: "Planner settings",
          values: (obj) => ({planner: obj?.planner})
        },
        {
          id: "production",
          name: "Production settings",
          values: (obj) => ({production: obj?.production})
        },
        {
          id: "cloud_apis",
          name: "Cloud APIs",
          children: [
            {
              id: "asaqc",
              name: "ASAQC",
              values: (obj) => ({value: obj?.cloud?.asaqc}),
            }
          ]
        }
      ],

      dirty: false,

      fileLoadDialog: false,

      viewMode: 0,
      dialog: false,
      contextMenu: false,
      contextMenuX: null,
      contextMenuY: null
    };
  },

  watch: {

    configuration: {
      handler (cur, prev) {
        if (cur && prev) {
          this.dirty = true;
        } else {
          this.dirty = false;
        }
      },
      deep: true
    },

    active (cur, prev) {
      if (cur == prev) {
        return;
      }

      if (!this.activeComponent && this.activeItem?.children?.length) {
        // Automatically expand this item
        if (!this.open.includes(cur)) {
          this.open.push(cur);
        }
        this.$nextTick( () => {
          const idx = this.active.findIndex(i => i == cur);
          if (idx != -1) {
            this.active.push(this.activeItem.children[0].id);
            this.active.splice(cur, 1);
          }
        });
      }

    }
  },

  computed: {

    activeComponent () {
      return components[this.active[0]];
    },

    activeItem () {
      function walk (tree) {
        const list = [];
        for (const leaf of tree) {
          if (leaf.children) {
            list.push(...walk(leaf.children));
          }
          list.push(leaf);
        }
        return list;
      }
      return walk(this.treeview).find(i => i.id === this.active[0]);
    },

    activeValues () {
      return this.activeItem?.values &&
        this.activeItem.values(this.configuration);
    },

    activeUpdateHandler () {
      return this.activeItem?.update ?? ((obj) => {
        console.warn("Unhandled update event on", this.activeItem?.id, obj);
      })
    },

    surveyState: {
      get () {
        return !this.configuration?.archived;
      },

      async set (value) {
        if (this.configuration) {
          await this.patch({archived: !value});
          // this.configuration.archived = !value;
        }
      }
    },

    fileInputErrors () {
      const messages = [];

      const validTypes = [
        "application/json",
        "application/yaml",
        "application/x-yaml"
      ];

      if (this.files instanceof File) {
        if (!validTypes.includes(this.files.type)) {
          messages.push(`Invalid file type: ${this.files.type}`);
          messages.push("Please select a JSON or YAML file");
        } else if (this.files.size < 32) { // 32 is an arbitrary small value
          messages.push("File too small to be a valid Dougal configuration");
        }
      } else if (this.files && this.files.length) {
        messages.push("Invalid file path");
      }

      return messages;
    },

    ...mapGetters(['user', 'writeaccess', 'loading', 'serverEvent'])
  },

  methods: {

    makeSection (obj, path, defaultVaue) {

      function reduced (obj = {}, path = []) {
        return path.reduce( (acc, cur) =>
          {
            if (!(cur in acc)) {
              acc[cur] = {} ;
            };
            return acc[cur]
          }, obj);
      }

      if (!obj) {
        obj = {};
      }

      if (typeof path == "string") {
        path = path.split(".");
      }

      let value = reduced(obj, path);

      const key = path.pop();
      if (!Object.keys(value ?? {}).length && defaultVaue !== undefined) {
        reduced(obj, path)[key] = defaultVaue;
      }
      return reduced(obj, path)[key];
    },

    async getConfiguration () {
      this.configuration = null;
      const url = `/project/${this.$route.params.project}/configuration`;
      const init = {
        headers: {
          "If-None-Match": "" // Ensure we get a fresh response
        }
      };
      this.configuration = await this.api([url, init]);
      this.dirty = false;
    },

    async getSettings () {
      this.settings = null;
      let url = `/project/${this.$route.params.project}/linename/properties`;
      const init = {
        headers: {
          "If-None-Match": "" // Ensure we get a fresh response
        }
      };
      this.settings = {
        lineNameBuilder: {
          properties: await this.api([url, init])
        }
      };
    },

    makeTree (obj, id=0) {
      const isObject = typeof obj === "object" && !Array.isArray(obj) && obj !== null;
      return isObject
        ? Object.keys(obj).map(k => {
          const children = this.makeTree(obj[k], id); //.filter(i => i !== null);
          return {
            id: id++,
            name: k,
            children
          }
        })
        : null;
    },

    deselect () {
      this.active.pop();
    },

    merge ([path, value]) {
      deepSet(this.configuration, path, value);
    },

    // Use to change the project's archival status
    async patch (data) {
      const url = `/project/${this.$route.params.project}/configuration`;
      const init = {
        method: "PATCH",
        body: data
      };
      const callback = async (err, res) => {
        if (!err && res.ok) {
          this.showSnack(["Configuration saved", "success"]);
        }
      };

      const refreshedConfiguration = await this.api([url, init, callback]);
      if (refreshedConfiguration) {
        this.configuration = refreshedConfiguration;
      }
    },

    async upload () {
      const url = `/project/${this.$route.params.project}/configuration`;
      const init = {
        method: "PUT",
        headers: {
          //"If-Match": "" // Ensure we're not overwriting someone else's changes
          "Content-Type": "application/json"
        },
        body: {...this.configuration, archived: true}
      };
      const res = await this.api([url, init]);
      if (res && res.id == this.configuration.id) {
        // In case the server decided to apply any changes
        this.showSnack(["Configuration uploaded to server", "success"]);
        this.$nextTick( () => {
          this.configuration = res;
          this.$nextTick( () => {
            this.dirty = false;
          });
        });
      }
    },

    async loadFromFile () {
      if (!this.fileInputErrors.length) {
        if (this.files.type == "application/json") {
          this.configuration = JSON.parse(await this.files.text());
          this.showSnack(["Configuration loaded from file", "primary"]);
        } else if (this.files.type == "application/yaml" || this.files.type == "application/x-yaml") {
          this.configuration = YAML.parse(await this.files.text());
          this.showSnack(["Configuration loaded from file", "primary"]);
        } else {
          console.error("Unknown file format (shouldn't happen)", this.files.type);
        }
        this.fileLoadDialog = false;
      }
    },

    async saveToFile () {
      const payload = YAML.stringify(this.configuration);
      const blob = new Blob([payload], {type: "application/yaml"});
      const url = URL.createObjectURL(blob);
      const filename = `${this.$route.params.project}-configuration.yaml`;

      const element = document.createElement('a');
      element.download = filename;
      element.href = url;
      element.click();
      URL.revokeObjectURL(url);
    },

    closeDialog () {
    },

    showContextMenu (e) {
      e.preventDefault();
      this.contextMenu = false
      this.contextMenuX = e.clientX
      this.contextMenuY = e.clientY
      this.$nextTick(() => {
        this.contextMenu = true
      })
    },

    ...mapActions(["api", "showSnack"])

  },

  async mounted () {
    this.getSettings();
    this.getConfiguration();
  },

}
</script>
