import { m as macro } from '../../macros2.js';
import DataAccessHelper from '../Core/DataAccessHelper.js';
import vtkDataArray from '../../Common/Core/DataArray.js';
import vtkPolyData from '../../Common/DataModel/PolyData.js';
import '../Core/DataAccessHelper/LiteHttpDataAccessHelper.js';

// import 'vtk.js/Sources/IO/Core/DataAccessHelper/HttpDataAccessHelper'; // HTTP + gz
// import 'vtk.js/Sources/IO/Core/DataAccessHelper/HtmlDataAccessHelper'; // html + base64 + zip
// import 'vtk.js/Sources/IO/Core/DataAccessHelper/JSZipDataAccessHelper'; // zip

// ----------------------------------------------------------------------------

const data = {};

// ----------------------------------------------------------------------------

function copyVector(src, srcOffset, dst, dstOffset, vectorSize) {
  for (let i = 0; i < vectorSize; i++) {
    dst[dstOffset + i] = src[srcOffset + i];
  }
}

// ----------------------------------------------------------------------------

function begin(splitMode) {
  data.splitOn = splitMode;
  data.pieces = [];
  data.v = [];
  data.vt = [];
  data.vn = [];
  data.f = [[]];
  data.size = 0;
}

// ----------------------------------------------------------------------------

function faceMap(str) {
  const idxs = str.split('/').map(i => Number(i));
  let vertexIdx = idxs[0] - 1;
  vertexIdx = vertexIdx < 0 ? vertexIdx + 1 + data.v.length / 3 : vertexIdx;
  let textCoordIdx = idxs[1] ? idxs[1] - 1 : vertexIdx;
  textCoordIdx = textCoordIdx < 0 ? textCoordIdx + 1 + data.vt.length / 2 : textCoordIdx;
  let vertexNormal = idxs[2] ? idxs[2] - 1 : vertexIdx;
  vertexNormal = vertexNormal < 0 ? vertexNormal + 1 + data.vn.length / 3 : vertexNormal;
  return [vertexIdx, textCoordIdx, vertexNormal];
}

// ----------------------------------------------------------------------------

function parseLine(line) {
  if (line[0] === '#') {
    return;
  }
  const tokens = line.split(/[ \t]+/);
  if (tokens[0] === data.splitOn) {
    tokens.shift();
    data.pieces.push(tokens.join(' ').trim());
    data.f.push([]);
    data.size++;
  } else if (tokens[0] === 'v') {
    data.v.push(Number(tokens[1]));
    data.v.push(Number(tokens[2]));
    data.v.push(Number(tokens[3]));
  } else if (tokens[0] === 'vt') {
    data.vt.push(Number(tokens[1]));
    data.vt.push(Number(tokens[2]));
  } else if (tokens[0] === 'vn') {
    data.vn.push(Number(tokens[1]));
    data.vn.push(Number(tokens[2]));
    data.vn.push(Number(tokens[3]));
  } else if (tokens[0] === 'f') {
    // Handle triangles for now
    if (data.size === 0) {
      data.size++;
    }
    const cells = data.f[data.size - 1];
    tokens.shift();
    const faces = tokens.filter(s => s.length > 0 && s !== '\r');
    const size = faces.length;
    cells.push(size);
    for (let i = 0; i < size; i++) {
      cells.push(faceMap(faces[i]));
    }
  }
}

// ----------------------------------------------------------------------------

function end(model) {
  const hasTcoords = !!data.vt.length;
  const hasNormals = !!data.vn.length;
  if (model.splitMode) {
    model.numberOfOutputs = data.size;
    for (let idx = 0; idx < data.size; idx++) {
      const polyIn = data.f[idx];
      const nbElems = polyIn.length;
      const nbPoints = data.v.length / 3;
      const keyPointId = {};
      let pointDuplicatesReferences;
      if (model.trackDuplicates) {
        // In trackDuplicates mode, we want the following point layout:
        // [pt0, pt1, pt2, ... ptN, pt0d1, pt0d2, pt1d1]
        const pointKeys = [];
        let duplicatesCount = 0;
        for (let offset = 0; offset < nbElems;) {
          const cellSize = polyIn[offset++];
          for (let pIdx = 0; pIdx < cellSize; pIdx++) {
            const [vIdx, tcIdx, nIdx] = polyIn[offset++];
            const key = `${vIdx}/${tcIdx}/${nIdx}`;
            if (keyPointId[key] === undefined) {
              if (pointKeys[vIdx] === undefined) {
                pointKeys[vIdx] = [key];
              } else {
                pointKeys[vIdx].push(key);
                ++duplicatesCount;
              }
              // will be overwritten for duplicates
              keyPointId[key] = vIdx;
            }
          }
        }
        pointDuplicatesReferences = new Uint16Array(nbPoints + duplicatesCount);
        let duplicates = 0;
        for (let pointId = 0; pointId < pointKeys.length; ++pointId) {
          const usageCount = pointKeys[pointId] ? pointKeys[pointId].length : 0;
          // Set the first duplicate index on the original point
          pointDuplicatesReferences[pointId] = usageCount > 1 ? nbPoints + duplicates : pointId;
          // Set the original index on each duplicated point
          for (let duplicateId = 1; duplicateId < usageCount; ++duplicateId) {
            const finalDuplicateId = nbPoints + duplicates++;
            pointDuplicatesReferences[finalDuplicateId] = pointId;
            // Associate the duplicate index to the key
            keyPointId[pointKeys[pointId][duplicateId]] = finalDuplicateId;
          }
        }
      }
      const ctMapping = {};
      const polydata = vtkPolyData.newInstance({
        name: data.pieces[idx]
      });
      const pts = [];
      const tc = [];
      const normals = [];
      const polys = [];
      let offset = 0;
      while (offset < nbElems) {
        const cellSize = polyIn[offset];
        polys.push(cellSize);
        for (let pIdx = 0; pIdx < cellSize; pIdx++) {
          const [vIdx, tcIdx, nIdx] = polyIn[offset + pIdx + 1];
          const key = `${vIdx}/${tcIdx}/${nIdx}`;
          if (ctMapping[key] === undefined) {
            const dstOffset = model.trackDuplicates ? keyPointId[key] : pts.length / 3;
            ctMapping[key] = dstOffset;
            copyVector(data.v, vIdx * 3, pts, dstOffset * 3, 3);
            if (hasTcoords) {
              copyVector(data.vt, tcIdx * 2, tc, dstOffset * 2, 2);
            }
            if (hasNormals) {
              copyVector(data.vn, nIdx * 3, normals, dstOffset * 3, 3);
            }
          }
          polys.push(ctMapping[key]);
        }
        offset += cellSize + 1;
      }
      polydata.getPoints().setData(Float32Array.from(pts), 3);
      if (model.trackDuplicates) {
        const duplicatesArray = vtkDataArray.newInstance({
          name: 'Duplicates',
          values: pointDuplicatesReferences
        });
        polydata.getPointData().addArray(duplicatesArray);
      }
      polydata.getPolys().setData(Uint32Array.from(polys));
      if (hasTcoords) {
        const tcoords = vtkDataArray.newInstance({
          numberOfComponents: 2,
          values: Float32Array.from(tc),
          name: 'TextureCoordinates'
        });
        polydata.getPointData().setTCoords(tcoords);
      }
      if (hasNormals) {
        const normalsArray = vtkDataArray.newInstance({
          numberOfComponents: 3,
          values: Float32Array.from(normals),
          name: 'Normals'
        });
        polydata.getPointData().setNormals(normalsArray);
      }

      // register in output
      model.output[idx] = polydata;
    }
  } else {
    model.numberOfOutputs = 1;
    const polydata = vtkPolyData.newInstance();
    polydata.getPoints().setData(Float32Array.from(data.v), 3);
    if (hasTcoords && data.v.length / 3 === data.vt.length / 2) {
      const tcoords = vtkDataArray.newInstance({
        numberOfComponents: 2,
        values: Float32Array.from(data.vt),
        name: 'TextureCoordinates'
      });
      polydata.getPointData().setTCoords(tcoords);
    }
    if (hasNormals && data.v.length === data.vn.length) {
      const normalsArray = vtkDataArray.newInstance({
        numberOfComponents: 3,
        values: Float32Array.from(data.vn),
        name: 'Normals'
      });
      polydata.getPointData().setNormals(normalsArray);
    }
    const polys = [];
    const polyIn = data.f[0];
    const nbElems = polyIn.length;
    let offset = 0;
    while (offset < nbElems) {
      const cellSize = polyIn[offset];
      polys.push(cellSize);
      for (let pIdx = 0; pIdx < cellSize; pIdx++) {
        const [vIdx] = polyIn[offset + pIdx + 1];
        polys.push(vIdx);
      }
      offset += cellSize + 1;
    }
    polydata.getPolys().setData(Uint32Array.from(polys));
    model.output[0] = polydata;
  }
}

// ----------------------------------------------------------------------------
// Static API
// ----------------------------------------------------------------------------

function getPointDuplicateIds(polyData, pointId) {
  const res = [];
  const duplicates = polyData.getPointData().getArrayByName('Duplicates');
  if (duplicates == null) {
    return res;
  }
  const duplicatesData = duplicates.getData();
  const originalPointId = Math.min(pointId, duplicatesData[pointId]);
  res.push(originalPointId);
  let duplicateId = duplicatesData[originalPointId];
  if (duplicateId !== originalPointId) {
    // point has duplicates
    while (duplicateId < duplicatesData.length && duplicatesData[duplicateId] === originalPointId) {
      // Duplicated points must be next to each other and original point must
      // reference first duplicate
      res.push(duplicateId++);
    }
  }
  return res;
}
const STATIC = {
  getPointDuplicateIds
};

// ----------------------------------------------------------------------------
// vtkOBJReader methods
// ----------------------------------------------------------------------------

function vtkOBJReader(publicAPI, model) {
  // Set our className
  model.classHierarchy.push('vtkOBJReader');

  // Create default dataAccessHelper if not available
  if (!model.dataAccessHelper) {
    model.dataAccessHelper = DataAccessHelper.get('http');
  }

  // Internal method to fetch Array
  function fetchData(url) {
    let option = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    return model.dataAccessHelper.fetchText(publicAPI, url, option);
  }

  // Set DataSet url
  publicAPI.setUrl = function (url) {
    let option = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    if (url.indexOf('.obj') === -1 && !option.fullpath) {
      model.baseURL = url;
      model.url = `${url}/index.obj`;
    } else {
      model.url = url;

      // Remove the file in the URL
      const path = url.split('/');
      path.pop();
      model.baseURL = path.join('/');
    }

    // Fetch metadata
    return publicAPI.loadData(option);
  };

  // Fetch the actual data arrays
  publicAPI.loadData = function () {
    let option = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
    return fetchData(model.url, option).then(content => publicAPI.isDeleted() ? false : publicAPI.parseAsText(content));
  };
  publicAPI.parseAsText = content => {
    if (!content) {
      return true;
    }
    if (content !== model.parseData) {
      publicAPI.modified();
    }
    model.parseData = content;
    model.numberOfOutputs = 0;
    begin(model.splitMode);
    content.split('\n').forEach(parseLine);
    end(model);
    return true;
  };
  publicAPI.requestData = (inData, outData) => {
    publicAPI.parseAsText(model.parseData);
  };

  // return Busy state
  publicAPI.isBusy = () => !!model.requestCount;
  publicAPI.getNumberOfOutputPorts = () => model.numberOfOutputs;
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
  numberOfOutputs: 1,
  requestCount: 0,
  splitMode: null,
  trackDuplicates: false
  // baseURL: null,
  // dataAccessHelper: null,
  // url: null,
};

// ----------------------------------------------------------------------------

function extend(publicAPI, model) {
  let initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  Object.assign(model, DEFAULT_VALUES, initialValues);

  // Build VTK API
  macro.obj(publicAPI, model);
  macro.get(publicAPI, model, ['url', 'baseURL']);
  macro.setGet(publicAPI, model, ['dataAccessHelper', 'splitMode', 'trackDuplicates']);
  macro.algo(publicAPI, model, 0, 1);
  macro.event(publicAPI, model, 'busy');

  // Object methods
  vtkOBJReader(publicAPI, model);
}

// ----------------------------------------------------------------------------

const newInstance = macro.newInstance(extend, 'vtkOBJReader');

// ----------------------------------------------------------------------------

var vtkOBJReader$1 = {
  newInstance,
  extend,
  ...STATIC
};

export { STATIC, vtkOBJReader$1 as default, extend, newInstance };
