content: /*****************************************************************************
   *                        Shapeways, Inc Copyright (c) 2014
   *                               Java Source
   *
   * This source is licensed under the GNU LGPL v2.1
   * Please read http://www.gnu.org/copyleft/lgpl.html for more information
   *
   * This software comes with the standard NO WARRANTY disclaimer for any
   * purpose. Use it at your own risk. If there's a problem you get to fix it.
   *
   ****************************************************************************/
  
  package abfab3d.io.input;
  
  import abfab3d.core.AttributeGrid;
  import abfab3d.mesh.IndexedTriangleSetBuilder;
  import abfab3d.mesh.WingedEdgeTriangleMesh;
  import abfab3d.util.TriangleMesh;
  import org.apache.commons.compress.archivers.ArchiveEntry;
  import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
  import org.apache.commons.io.FilenameUtils;
  import org.w3c.dom.Document;
  import org.w3c.dom.Element;
  import org.w3c.dom.NodeList;
  import org.xml.sax.SAXException;
  
  import javax.xml.parsers.DocumentBuilder;
  import javax.xml.parsers.DocumentBuilderFactory;
  import javax.xml.parsers.ParserConfigurationException;
  import java.io.IOException;
  import java.io.InputStream;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.zip.ZipEntry;
  import java.util.zip.ZipFile;
  
  import static abfab3d.core.Output.printf;
  
  /**
   * SVX Reader.
   *
   * Reads the Simple Triangle Shells format.
   *
   * @author Alan Hudson
   */
  public class STSReader {
      /** The manifest for the last load call */
      private STSManifest mf;
  
      /**
       * Load a STS file into a grid.
       *
       * @param file The zip file
       * @return
       * @throws java.io.IOException
       */
      public TriangleMesh[] loadMeshes(String file) throws IOException {
          ZipFile zip = null;
          TriangleMesh[] ret_val = null;
  
          try {
              zip = new ZipFile(file);
  
              ZipEntry entry = zip.getEntry("manifest.xml");
              if (entry == null) {
                  throw new IOException("Cannot find manifest.xml in top level");
              }
  
              InputStream is = zip.getInputStream(entry);
              mf = parseManifest(is);
  
              if (mf == null) {
                  throw new IOException("Could not parse manifest file");
              }
  
              List<STSPart> plist = mf.getParts();
              int len = plist.size();
  
              ret_val = new TriangleMesh[len];
              for(int i=0; i < len; i++) {
                  STSPart part = plist.get(i);
                  ZipEntry ze = zip.getEntry(part.getFile());
                  MeshReader reader = new MeshReader(zip.getInputStream(ze),"", FilenameUtils.getExtension(part.getFile()));
                  IndexedTriangleSetBuilder its = new IndexedTriangleSetBuilder();
                  reader.getTriangles(its);
  
                  // TODO: in this case we could return a less heavy triangle mesh struct?
                  WingedEdgeTriangleMesh mesh = new WingedEdgeTriangleMesh(its.getVertices(), its.getFaces());
                  ret_val[i] = mesh;
              }
  
              return ret_val;
          } finally {
              if (zip != null) zip.close();
          }
      }
  
      /**
       * Load a STS file into a grid.
       *
       * @param is The stream
       * @return
       * @throws java.io.IOException
       */
      public TriangleMesh[] loadMeshes(InputStream is) throws IOException {
          ZipArchiveInputStream zis = null;
          TriangleMesh[] ret_val = null;
  
          try {
              zis = new ZipArchiveInputStream(is);
  
              ArchiveEntry entry = null;
              int cnt = 0;
  
              while ((entry = zis.getNextEntry()) != null) {
                  // TODO: not sure we can depend on this being first
                  if (entry.getName().equals("manifest.xml")) {
                      mf = parseManifest(zis);
  
                      ret_val = new TriangleMesh[mf.getParts().size()];
                  } else {
                      MeshReader reader = new MeshReader(zis, "", FilenameUtils.getExtension(entry.getName()));
                      IndexedTriangleSetBuilder its = new IndexedTriangleSetBuilder();
                      reader.getTriangles(its);
  
                      // TODO: in this case we could return a less heavy triangle mesh struct?
                      WingedEdgeTriangleMesh mesh = new WingedEdgeTriangleMesh(its.getVertices(), its.getFaces());
                      ret_val[cnt++] = mesh;
                  }
              }
          } finally {
              zis.close();
          }
  
          return ret_val;
      }
  
      /**
       * Load a STS file into a grid.
       *
       * @param file The zip file
       * @return
       * @throws java.io.IOException
       */
      public AttributeGrid loadGrid(String file) throws IOException {
  
          ZipFile zip = null;
  
          try {
              zip = new ZipFile(file);
  
              ZipEntry entry = zip.getEntry("manifest.xml");
              if (entry == null) {
                  throw new IOException("Cannot find manifest.xml in top level");
              }
  
              InputStream is = zip.getInputStream(entry);
              mf = parseManifest(is);
  
              if (mf == null) {
                  throw new IOException("Could not parse manifest file");
              }
  /*
              AttributeGrid ret_val = new ArrayAttributeGridByte(mf.getGridSizeX(),mf.getGridSizeY(),mf.getGridSizeZ(),mf.getVoxelSize(),mf.getVoxelSize());
  
              double[] bounds = new double[6];
  
              bounds[0] = mf.getOriginX();
              bounds[1] = mf.getOriginX() + mf.getGridSizeX() * mf.getVoxelSize();
              bounds[2] = mf.getOriginY();
              bounds[3] = mf.getOriginY() + mf.getGridSizeY() * mf.getVoxelSize();
              bounds[4] = mf.getOriginZ();
              bounds[5] = mf.getOriginZ() + mf.getGridSizeZ() * mf.getVoxelSize();
              ret_val.setGridBounds(bounds);
  
              List<Channel> channels = mf.getChannels();
  
              for(Channel chan : channels) {
                  if (chan.getType().getId() == Channel.Type.DENSITY.getId()) {
                      SlicesReader sr = new SlicesReader();
                      sr.readSlices(ret_val,zip,chan.getSlices(),0,0,mf.getGridSizeY());
                  }
              }
  */
              return null;
          } finally {
              if (zip != null) zip.close();
          }
      }
  
      public STSManifest getManifest() {
          return mf;
      }
  
      /**
       * Parse the manifest file
       * @param src The manifest file
       * @return
       */
      private STSManifest parseManifest(InputStream src) throws IOException {
  
          String field = null;
          String val = null;
  
          try {
              DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
              DocumentBuilder builder = factory.newDocumentBuilder();
  
              Document doc = builder.parse(src);
  
              NodeList model_list = doc.getElementsByTagName("model");
  
              if (model_list == null) {
                  throw new IllegalArgumentException("File contains no model element");
              }
  
              Element model = (Element) model_list.item(0);
  
              if (model == null) {
                  throw new IllegalArgumentException("File contains no model element");
              }
  
              field = "units";
              val = model.getAttribute(field);
              String units = val;
  
              if (units != null && units.length() != 0) {
                  mf.setUnits(Double.parseDouble(units));
              }
  
              STSManifest ret_val = new STSManifest();
              int len;
              field = "parts";
  
              NodeList part_list = doc.getElementsByTagName("part");
  
              if (part_list == null) {
                  throw new IllegalArgumentException("File contains no part elements");
              }
  
              len = part_list.getLength();
  
              ArrayList<STSPart> plist = new ArrayList<STSPart>();
              for(int i=0; i < len; i++) {
                  Element part = (Element) part_list.item(i);
  
                  field = "file";
                  val = part.getAttribute(field);
                  String file = val;
  
                  field = "material";
                  val = part.getAttribute(field);
                  String material = val;
  
                  plist.add(new STSPart(file, material));
              }
  
              ret_val.setParts(plist);
  
              NodeList metadata_list = doc.getElementsByTagName("entry");
  
              NodeList material_list = doc.getElementsByTagName("material");
  
              if (material_list != null) {
                  HashMap<String,String> map = new HashMap<String,String>();
                  len = material_list.getLength();
  
                  for(int i=0; i < len; i++) {
                      Element entry = (Element) material_list.item(i);
                      String key = entry.getAttribute("id");
                      String value = entry.getAttribute("urn");
  
                      if (key != null && key.length() > 0 && value != null && value.length() > 0) {
                          map.put(key, value);
                      }
                  }
                  ret_val.setMaterials(map);
              }
  
              if (metadata_list != null) {
                  HashMap<String,String> map = new HashMap<String,String>();
                  len = metadata_list.getLength();
  
                  for(int i=0; i < len; i++) {
                      Element entry = (Element) metadata_list.item(i);
                      String key = entry.getAttribute("key");
                      String value = entry.getAttribute("value");
  
                      if (key != null && key.length() > 0 && value != null && value.length() > 0) {
                          map.put(key, value);
                      }
                  }
                  ret_val.setMetadata(map);
              }
  
              return ret_val;
          } catch(ParserConfigurationException pce) {
              pce.printStackTrace();
          } catch(SAXException saxe) {
              saxe.printStackTrace();
          } catch(Exception e) {
              printf("Cannot parse field: %s  val: %s\n",field,val);
              e.printStackTrace();
          }
  
          return null;
      }
  }
  