import {RemoteSeismicDataSource} from '@int/geotoolkit/seismic/data/RemoteSeismicDataSource';
import {BrowserInfo} from '@int/geotoolkit/util/BrowserInfo';
import {NormalizationType} from '@int/geotoolkit/seismic/pipeline/NormalizationType';
import {mergeObjects, log, warn} from '@int/geotoolkit/base';
import {Plot} from '@int/geotoolkit3d/Plot';
import {Vector3, Vector2, LineBasicMaterial, BufferGeometry, BufferAttribute, Line, Box3} from '@int/geotoolkit3d/THREE';
import {Grid} from '@int/geotoolkit3d/scene/grid/Grid';
import {IndexCoordinates} from '@int/geotoolkit3d/transformation/IndexCoordinates';
import {XYCoordinates} from '@int/geotoolkit3d/transformation/XYCoordinates';
import {Volume} from '@int/geotoolkit3d/scene/seismic/Volume';
import {Slice} from '@int/geotoolkit3d/scene/seismic/Slice';
import {SliceMaterial} from '@int/geotoolkit3d/scene/seismic/SliceMaterial';
import {Helper} from '@int/geotoolkit3d/util/Helper';
import {CursorTool} from '@int/geotoolkit3d/tool/cursor/CursorTool';
import {CrossHairCursor} from '@int/geotoolkit3d/tool/cursor/CrossHairCursor';
import {Status as AbstractMoveToolStatus} from '@int/geotoolkit3d/tool/AbstractMoveTool';
import {SeismicSliderTool} from '@int/geotoolkit3d/tool/seismic/SeismicSliderTool';
import {Object3D} from '@int/geotoolkit3d/scene/Object3D';
import {LineStyle} from "@int/geotoolkit/attributes/LineStyle";
import {TextStyle} from "@int/geotoolkit/attributes/TextStyle";
import {IndexGrid} from "@int/geotoolkit3d/scene/seismic/IndexGrid";
// import temp from "../seismic/temp.Sgy"
function error (e) {
  warn('Error: ' + JSON.stringify(e));
}
const intgeoserver = 'https://demo.int.com/INTGeoServer/';
// const intgeoserver = process.env.VUE_APP_BASE_API + '/ndy/dz/qRemoteSeismicReader/';

const filename = 'Gullfaks_Amplitude';

function roundToStep (val, step, start) {
  if (start === undefined) start = 0;
  val -= start;
  return Math.round(val / step) * step + start;
}

export class SeismicData {
  constructor (plot, onfailure) {
    this.plot = plot;
    this.data = new RemoteSeismicDataSource({
      'file': 'data/seismic/' + filename + '.xgy',
      'host': intgeoserver + 'json',
      'version': 2
    });
    // this.data = new RemoteSeismicDataSource({
    //   'file': temp,
    //   // 'host': intgeoserver + 'json',
    //   'version': 2
    // });
    console.log("this.data",this.data)
    this.iKey = null;
    this.jKey = null;
    this.sampleKey = null;
    this.plot.setOptions({'scale': new Vector3(1.0, 1.0, 2.2)});
    this.plot.updateModelLimits();
    const size = new Vector2();
    const ml = this.plot.getModelLimits().expandByVector(this.plot.getModelLimits().getSize(size).multiplyScalar(0.1));
    const mlLabels = ml.clone();
    console.log("ml",ml)
    console.log("mlLabels",mlLabels)
    const options = {
      start: new Vector3(ml.min.x, ml.min.y, -4000),
      end: new Vector3(ml.max.x, ml.max.y, 0),
      modelStart: new Vector3(mlLabels.min.x, mlLabels.min.y, 4000),
      modelEnd: new Vector3(mlLabels.max.x, mlLabels.max.y, 0)
      // start: ml.min,
      // end: ml.max,
      // modelStart: mlLabels.min,
      // modelEnd: mlLabels.max
    };
    console.log("options",options)
    this.grid = new Grid(options);
    console.log('grid',this.grid)
    this.plot.getRoot().add(this.grid);
    this.cursor = new CursorTool()
      .setOptions({
        'cursor': new CrossHairCursor({
          'limits': ml
        })
      });
    this.plot.addTool(this.cursor);
    this.seismicMoveTool = new SeismicSliderTool({
      'name': 'SeismicSliderTool'
    });
    this.plot.addTool(this.seismicMoveTool);
    this.enableController = function (enabled) {
      this.cursor.setOptions({
        'enabled': enabled
      });
      this.plot.setOptions({
        'camera': {
          'controller': {
            'options': {
              'enabled': enabled
            }
          }
        }
      });
    };

    const material = new LineBasicMaterial({'color': 0xff0000});
    const selectionObject = new Object3D();
    const geometry = new BufferGeometry();
    // create a simple square shape. We duplicate the top left and bottom right
    // vertices because each vertex needs to appear once per triangle.
    const vertices = new Float32Array( [
      -0.5, -0.5, -0.5,
      0.5, -0.5, -0.5,
      0.5, 0.5, -0.5,
      -0.5, 0.5, -0.5,
      -0.5, -0.5, -0.5,

      -0.5, -0.5, 0.5,
      0.5, -0.5, 0.5,
      0.5, -0.5, -0.5,
      -0.5, -0.5, -0.5,

      -0.5, -0.5, 0.5,
      -0.5, 0.5, 0.5,
      -0.5, 0.5, -0.5,
      -0.5, -0.5, -0.5

    ] );

    // itemSize = 3 because there are 3 values (components) per vertex
    geometry.setAttribute( 'position', new BufferAttribute( vertices, 3 ) );


    const g = new Line(geometry, material);
    selectionObject.add(g);
    // Listen to the tool's events
    let previous = null;
    this.seismicMoveTool.addMoveListener((status, object, operation) => {
      if (status === AbstractMoveToolStatus.Selected) {
        if (object == null) {
          if (previous != null) {
            previous.remove(selectionObject);
          }
          this.enableController(true);
        } else {
          object.add(selectionObject);
          previous = object;
          this.enableController(false);
        }
      }
      if (status === AbstractMoveToolStatus.Start) {
        if (object != null && object.parent != null) {
          // White out texture since its moving
          object.parent.getSliceMaterial().resetTiles();
        }
      }

      if (status === AbstractMoveToolStatus.End) {
        this.enableController(status === AbstractMoveToolStatus.End);
        if (object != null) {
          const slice = object.parent;
          const location = slice.getSliceLocation();
          const callback = function (reader) {
            slice.setOptions({
              'data': {
                'reader': reader
              }
            });
          };

          if (location['i'] != null) {
            this.createInlineReader(location, 2, callback);
          } else if (location['j'] != null) {
            this.createXlineReader(location, 2, callback);
          } else if (location['z'] != null) {
            this.createHSliceReader(location, 1, callback);
          }
          if (previous != null) {
            previous.remove(selectionObject);
          }
        }
      }
    });
    const onServerResponse = function () {
      this.catchKeys();
      this.onServerResponse();
    }.bind(this);

    // Fetch the metadata (asynchronous)
    this.data.open(
      onServerResponse, () => {
        onfailure(this);
      }
    );
  }

  resetCamera () {
    let animate = true;
    const modelLimits = this.plot.getModelLimits();
    const size = new Vector2();
    const dist = modelLimits.getSize(size).length();
    const point = new Vector3();
    modelLimits.getCenter(point).add(new Vector3(dist, dist, dist / 2));
    const currentPos = this.plot.getCameraLocation();
    currentPos.sub(point);
    const magnitude = currentPos.length();
    const time = magnitude / dist;
    if (time > 35) {
      animate = false;
    }
    const lookAt = new Vector3();
    this.plot.moveCamera(point, modelLimits.getCenter(lookAt), animate, time * 1000);
    return this;
  }

  onServerResponse (seismicdata) {
    const seismicIJIndex = this.getIndex();
    let pixelScale = 1.0;
    if (BrowserInfo.isIE()) {
      pixelScale = 0.5;
    }
    // callback to update the camera and gridLimits
    const callback = function () {
      if (this.plot.isDisposed()) return;
      this.updateGridLimits();
      this.resetCamera();
    }.bind(this);

    // Seismicdata has successfuly be opened, create a volume and start displaying some slices
    const seismicVolume = new Volume({
      'index': seismicIJIndex,
      'xy': this.getXY()
    });
    this.plot.getRoot().add(seismicVolume);
    this.seismicMoveTool.setVolume(seismicVolume);
    function addSliceToVolume (reader) { //添加切片
      // Context bound when using this function
      const self = this;
      // Creates and add a seismic slice
      self.seismicvolume.add(new Slice({
        'material': new SliceMaterial({
          'horizontalresolution': pixelScale,
          'verticalresolution': pixelScale,
          'data': {
            'reader': reader
          },
          'pipeline': {
            'options': {
              'normalization': {
                'type': NormalizationType.RMS,
                'scale': 0.4
              },
              'colors': {
                // 'colorMap': 'RedWhiteBlack'
                'colorMap': 'WhiteBlack'
              }
            }
          },
          'oninvalidate': self.seismicvolume.invalidateObject.bind(self.seismicvolume)
        }),
        'index': self.seismicdata.getIndex(),
        'slicelocation': self.location
      }));
      self.callback();
    }

    // Fetch an inline and display it
    const center = new Vector3();
    seismicIJIndex.asBox3().getCenter(center);

    const inlineLocation = {'i': center.x};
    this.createInlineReader(inlineLocation, 2, addSliceToVolume.bind({
      location: inlineLocation,
      seismicvolume: seismicVolume,
      seismicdata: this,
      callback: callback
    }), error);

    // Fetch an xline and display it
    const xlineLocation = {'j': center.y};
    this.createXlineReader(xlineLocation, 2, addSliceToVolume.bind({
      location: xlineLocation,
      seismicvolume: seismicVolume,
      seismicdata: this,
      callback: callback
    }), error);

    // Fetch a timeslice/depthslice and display it
    const hLocation = {'z': center.z / seismicIJIndex.getSampleRate()};
    hLocation.z = roundToStep(hLocation.z, seismicIJIndex.getSampleRate(), seismicIJIndex.asBox3().min.z);
    this.createHSliceReader(hLocation, 1, addSliceToVolume.bind({
      location: hLocation,
      seismicvolume: seismicVolume,
      seismicdata: this,
      callback: callback
    }), error);
  }


  /**
   * Process keys from response
   * @private
   */
  catchKeys () {
    const workingData = this.data.getMetaData();
    console.log('workingData',workingData)
    const keys = this.data.getKeys();
    console.log('keys',keys)
    let iName, jName, i;
    const indexType = workingData.getIndexType();
    console.log('indexType',indexType)
    const volumeKeyNames = workingData.getVolumeKeyNames();
    console.log('volumeKeyNames',volumeKeyNames)
    if (indexType.toLowerCase() === 'volume' && volumeKeyNames != null) {
      iName = volumeKeyNames['i'];
      jName = volumeKeyNames['j'];
    } else {
      const defaultKeyNames = workingData.getVolumeDefaultKeyNames();
      if (defaultKeyNames) {
        iName = defaultKeyNames['i'];
        jName = defaultKeyNames['j'];
      } else {
        // build candidate keys
        const candidates = [];

        for (i = 0; i < keys.length; ++i) {
          if (!keys[i]['sampleKey'] && keys[i]['key'].toLowerCase() !== 'tracenumber') {
            candidates.push(keys[i]['key']);
          }
        }
        if (candidates.length === 2) {
          iName = candidates[0];
          jName = candidates[1];
        } else if (candidates.length <= 1) {
          log('Not enough valid index keys');
          return;
        } else {
          // Grab the first two, generally we would ask which ones to use
          // This demo doesn't need that functionality right now - it targets a single file
          iName = candidates[0];
          jName = candidates[1];
        }
      }
    }
    const samples = [];
    for (i = 0; i < keys.length; ++i) {
      if (keys[i]['sampleKey']) {
        samples.push(i);
      }
    }

    // For this demo we just grab the first sample key.
    if (samples.length > 0) {
      this.sampleKey = samples[0];
    }
    //
    //
    for (i = 0; i < keys.length; i++) {
      if (keys[i]['key'] === iName) {
        this.iKey = i;
      }
      if (keys[i]['key'] === jName) {
        this.jKey = i;
      }
    }
    //
    return;
  }

  updateGridLimits () {
    let limits = Helper.computeWorldBoundingBox(
      this.plot.getRoot(),
      ((child) => child !== this.grid));  // Ignore grid to compute grid limits :)

    if (limits == null) limits = new Box3(new Vector3(0, 0, -4000), new Vector3(4000, 4000, 0));

    this.plot.rootScaleInv(limits.min);
    this.plot.rootScaleInv(limits.max);
    console.log('limits',limits)
    this.grid.setOptions({
      'start': limits.min,
      'end': limits.max,
      'modelstart': limits.min.clone().multiply(new Vector3(1, 1, -1)), // Display Z axis as depth, not elevation
      'modelend': limits.max.clone().multiply(new Vector3(1, 1, -1)) // Display Z axis as depth, not elevation
    });
    this.cursor.getCursor().setOptions({
      'limits': limits
    });
    this.plot.updateModelLimits();
  }

  /**
   * Returns the underlying seismic data source.
   * @returns {RemoteSeismicDataSource}
   */
  getDatasource () {
    return this.data;
  }

  createInlineReader (index, decimation, success, error) {
    if (this.iKey == null || this.jKey == null) {
      return;
    }
    const key = this.data.getKeys()[this.iKey];
    const oppositeKey = this.data.getKeys()[this.jKey];
    this.createVSliceReader(index['i'], key, oppositeKey, decimation, success, error);
  }

  createXlineReader (index, decimation, success, error) {
    if (this.iKey == null || this.jKey == null) {
      return;
    }
    const key = this.data.getKeys()[this.jKey];
    const oppositeKey = this.data.getKeys()[this.iKey];
    this.createVSliceReader(index['j'], key, oppositeKey, decimation, success, error);
  }

  createVSliceReader (index, key, oppositeKey, decimation, success, error) {
    const requestKey = JSON.parse(JSON.stringify(key)); // clone key
    requestKey['position'] = index;
    this.data.select(this.createVSectionQuery(requestKey, oppositeKey, decimation),
      (reader) => {
        success(reader);
      },
      () => {
        error();
      }
    );
  }

  createHSliceReader (index, ijbbox, success, error) {
    if (this.sampleKey == null) {
      return null;
    }
    const keys = this.data.getKeys();
    const key = keys[this.sampleKey];
    const requestKey = JSON.parse(JSON.stringify(key)); // clone key
    requestKey['position'] = key['min'] + index['z'] * key['increment'];

    this.data.select(this.createHSectionQuery(requestKey),
      (reader) => {
        success(reader);
      },
      () => {
        error();
      }
    );
    return null;
  }


  /**
   * Returns the IJ coordinates.<br>
   * @returns {IndexCoordinates}
   */
  getIndex () {
    const keys = this.data.getKeys();

    const json = {};

    if (this.iKey != null) {
      const inlineKey = keys[this.iKey];
      mergeObjects({
        'i0': inlineKey['min'],
        'icount': (inlineKey['max'] - inlineKey['min']) / inlineKey['increment'] + 1,
        'istep': inlineKey['increment']
      }, json);
    }
    if (this.jKey != null) {
      const xlineKey = keys[this.jKey];
      mergeObjects({
        'j0': xlineKey['min'],
        'jcount': (xlineKey['max'] - xlineKey['min']) / xlineKey['increment'] + 1,
        'jstep': xlineKey['increment']
      }, json);
    }
    if (this.sampleKey != null) {
      const zKey = keys[this.sampleKey];
      mergeObjects({
        'z0': zKey['min'],
        'z1': zKey['max'],
        'samplerate': zKey['increment']
      }, json);
    }
    return new IndexCoordinates().fromJSONIndex(json);
  }
  /**
   * Returns the XY coordinates.<br>
   * @returns {XYCoordinates}
   */
  getXY () {
    if (this.iKey == null || this.jKey == null) {
      return null;
    }
    const grid = this.data.getDataInfo()['gridCoordinates'];
    const keys = this.data.getKeys();
    const inlineKey = keys[this.iKey];
    const xlineKey = keys[this.jKey];

    const xy = {};
    for (let i = 0; i < grid.length; i++) {
      const gridValues = grid[i]['values'];
      if (gridValues[1] === xlineKey['min'] && gridValues[0] === inlineKey['min']) {
        xy['x0'] = gridValues[2];
        xy['y0'] = gridValues[3];
      } else if (gridValues[1] === xlineKey['min'] && gridValues[0] === inlineKey['max']) {
        xy['x1'] = gridValues[2];
        xy['y1'] = gridValues[3];
      } else if (gridValues[1] === xlineKey['max'] && gridValues[0] === inlineKey['max']) {
        xy['x2'] = gridValues[2];
        xy['y2'] = gridValues[3];
      } else if (gridValues[1] === xlineKey['max'] && gridValues[0] === inlineKey['min']) {
        xy['x3'] = gridValues[2];
        xy['y3'] = gridValues[3];
      }
    }

    return new XYCoordinates().fromJSONXY(xy);
  }

  /**
   * Builds the query object to get the full vertical slice of the given key
   * @param {object} key The key json, same format that the datasource internal keys
   * @param {object} oppositeKey The 'opposite' key, meaning the XLINE key if the requested key is INLINE
   * @param {number} traceDecimationFactor A decimation factor to reduce the amount of traces fetched
   * @returns {object}
   */
  createVSectionQuery (key, oppositeKey, traceDecimationFactor) {
    const selectKeys = [];
    // round to the closest step, just in case
    const pos = roundToStep(key['position'], key['increment'], key['min']);
    selectKeys[0] = {
      'name': key['key'],
      'min': pos,
      'max': pos,
      'step': key['increment'],
      'order': 'asc',
      'position': pos
    };

    selectKeys[1] = {
      'name': oppositeKey['key'],
      'min': oppositeKey['min'],
      'max': oppositeKey['max'],
      'step': oppositeKey['increment'] * traceDecimationFactor,
      'order': 'asc'
    };
    return {
      'keys': selectKeys,
      'options': null,
      'emptyTracesKey': {
        'name': oppositeKey['key'],
        'min': oppositeKey['min'],
        'max': oppositeKey['max']
      }
    };
  }

  /**
   * Builds the query object to get the full horizontal slice of the given key
   * @param {object} key The key json, same format that the datasource internal keys
   * @returns {object}
   */
  createHSectionQuery (key) {
    const selectKeys = [];
    // round to the closest step, just in case
    const pos = roundToStep(key['position'], key['increment'], key['min']);
    selectKeys[0] = {
      'name': key['key'],
      'min': pos,
      'max': pos,
      'step': key['increment'],
      'position': pos,
      'order': ''
    };
    return {
      'keys': selectKeys,
      'options': null,
      'traceOrder': 2
    };
  }
}
