package org.jmol.symmetry;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.Map;

import org.jmol.api.SymmetryInterface;
import org.jmol.modelset.Atom;
import org.jmol.util.BSUtil;
import org.jmol.util.SimpleUnitCell;
import org.jmol.viewer.FileManager;
import org.jmol.viewer.JC;
import org.jmol.viewer.Viewer;

import javajs.util.BS;
import javajs.util.Lst;
//import javajs.util.M3;
import javajs.util.P3;

/**
 * A relatively simple space group finder given a unit cell.
 * The unit cell is used to reduce the options involving hexagonal, tetragonal, and cubic groups.
 * 
 * See https://stokes.byu.edu/iso/findsymform.php for finding the space group
 * given no unit cell information.
 * 
 */
public class SpaceGroupFinder {

  /**
   * maximum allowable supercell
   */
  private static final int MAX_COUNT = 100;


  /**
   * maximum allowable distance fx, fy ,fz for atom finder
   */
  private static final float SLOP0014 = (float) Math.sqrt(JC.UC_TOLERANCE2);

  /**
   * tolerance for fractional coord and 
   */
  private final static float //SLOP02 = 0.02f, 
      SLOP001 = 0.001f, SLOP0001= 0.0001f;  // 0.001 was too tight for labradorite Si // 0.0001 was too tight here. 

  private static int GROUP_COUNT; // 530
  private static int OP_COUNT; // 882
  private static BS[] bsOpGroups;
  private static BS[] bsGroupOps;
  private static String[] groupNames;
  private static String[] opXYZ;
  private static SymmetryOperation[] ops;

  private static BufferedReader rdr = null;

  private SGAtom[] atoms;
  private int nAtoms;
  private P3 pt = new P3();

  public SpaceGroupFinder() {
  }

  private class SGAtom extends P3 {
    int typeAndOcc;
    int index;
    String name;

    SGAtom(int type, int index, String name, int occupancy) {
      this.typeAndOcc = type + 1000 * occupancy;
      this.index = index;
      this.name = name;
    }
  }

  public Object findSpaceGroup(Viewer vwr, BS atoms0, String opXYZ0,
                               SymmetryInterface uc, boolean asString) {
    P3[] oabc = null;
    Atom[] cartesians = vwr.ms.at;
    int isg = 0;
    BS bsGroups = new BS();
    BS bsOps = new BS();
    BS bsAtoms = null;
    BS bsPoints0 = new BS();
    if (opXYZ0 == null) {
      bsAtoms = BSUtil.copy(atoms0);
      nAtoms = bsAtoms.cardinality();
    }
    BS targets = BS.newN(nAtoms);
    int n = 0;
    int nChecked = 0;
    P3 scaling = P3.new3(1, 1, 1);
//    BS withinCell = null;
    try {
      if (bsOpGroups == null)
        loadData(vwr, this);
      if (opXYZ0 != null) {
        return getGroupsWithOp(opXYZ0);
      }
      oabc = uc.getUnitCellVectors();
      uc = uc.getUnitCellMultiplied();
      filterGroups(bsGroups, uc);
//      withinCell = vwr.ms.getAtoms(T.unitcell, uc);
      //BS extraAtoms = BSUtil.copy(bsAtoms);
      //extraAtoms.andNot(withinCell);
//      withinCell.and(bsAtoms);
      //int nExtra = extraAtoms.cardinality();

      // 1. Get ALL atoms.

      atoms = new SGAtom[bsAtoms.cardinality()];
      System.out.println("bsAtoms = " + bsAtoms);
      for (int p = 0, i = bsAtoms.nextSetBit(0); i >= 0; i = bsAtoms
          .nextSetBit(i + 1), p++) {
        Atom a = cartesians[i];
        int type = a.getAtomicAndIsotopeNumber();
        (atoms[p] = new SGAtom(type, i, a.getAtomName(), a.getOccupancy100())).setT(toFractional(a, uc));

      }
      BS bsPoints = BSUtil.newBitSet2(0, nAtoms);

      // Look out for tetrgonal aac issue -- abandonded

      SymmetryInterface uc0 = uc;
//      M3 mtet = new M3();
//      uc = checkTetragonal(vwr, uc, mtet);
//      if (uc == uc0) {
//        mtet = null;
//      } else {
//        System.out.println("tetragonoal setting issue detected");
//      }

//      // 2. Check that packing atoms, if any, are complete and only those packed.
      // decided this was unnecessary. We can just unitize and remove duplicates

//      if (nExtra > 0) {
//        BS packedAtoms = new BS();
//        checkPackingAtoms(bsPoints, extraAtoms, 1, packedAtoms);
//        checkPackingAtoms(bsPoints, extraAtoms, 2, packedAtoms);
//        checkPackingAtoms(bsPoints, extraAtoms, 3, packedAtoms);
//        if (packedAtoms.cardinality() == nExtra) {
//          bsPoints.andNot(packedAtoms);
//        } else {
//          System.out.println("Packing check failed! " + nExtra + " extra atoms " + extraAtoms + ", but " + packedAtoms.cardinality() + " found for " + packedAtoms);
//          bsPoints.clearAll();
//        }
//      }

      // 3. Unitize, remove duplicates, and check for supercells in each direction

      nAtoms = bsPoints.cardinality();
      uc0 = uc;
      boolean isSupercell = false;
      if (nAtoms > 0) {
        for (int i = bsPoints.nextSetBit(0); i >= 0; i = bsPoints
            .nextSetBit(i + 1)) {
          SimpleUnitCell.unitizeDim(3, atoms[i]);
        }
        removeDuplicates(bsPoints);
        uc = checkSupercell(vwr, uc, bsPoints, 1, scaling);
        uc = checkSupercell(vwr, uc, bsPoints, 2, scaling);
        uc = checkSupercell(vwr, uc, bsPoints, 3, scaling);
        isSupercell = (uc != uc0);
        if (isSupercell) {
          if (scaling.x != 1)
            System.out.println("supercell found; a scaled by 1/" + scaling.x);
          if (scaling.y != 1)
            System.out.println("supercell found; b scaled by 1/" + scaling.y);
          if (scaling.z != 1)
            System.out.println("supercell found; c scaled by 1/" + scaling.z);
        }
      }

      // 4. Remove unneeded atoms and recalculate fractional position if a supercell

      n = bsPoints.cardinality();
      bsAtoms = new BS();
      SGAtom[] newAtoms = new SGAtom[n];
      for (int p = 0, i = bsPoints.nextSetBit(0); i >= 0; i = bsPoints
          .nextSetBit(i + 1)) {
        SGAtom a = atoms[i];
        newAtoms[p++] = a;
        if (isSupercell) {
          a.setT(toFractional(cartesians[a.index], uc));
          SimpleUnitCell.unitizeDim(3, a);
//          if (mtet != null)
//            mtet.rotate(a);
        }
        bsAtoms.set(atoms[i].index);
      }

      atoms = newAtoms;
      nAtoms = n;
      System.out.println("bsAtoms(within cell) = " + bsAtoms);
      bsPoints.clearAll();
      bsPoints.setBits(0, nAtoms);
      bsPoints0 = BS.copy(bsPoints);
      BS temp1 = BS.newN(OP_COUNT);
      BS targeted = BS.newN(nAtoms);

      bsOps.setBits(1, OP_COUNT);
      if (nAtoms == 0) {
        bsGroups.clearBits(1, GROUP_COUNT);
        bsOps.clearAll();
      }

      // 5. Adaptively iterate over all known operations.

      
      BS uncheckedOps = BS.newN(OP_COUNT);
      BS opsChecked = BS.newN(OP_COUNT);
      opsChecked.set(0);
      boolean hasC1 = false;
      for (int iop = bsOps.nextSetBit(1);iop > 0 && bsGroups.cardinality() > 0; iop = bsOps.nextSetBit(iop + 1)) {
        SymmetryOperation op = getOp(iop);
        System.out.println("\nChecking operation " + iop + " " + opXYZ[iop]);
        System.out.println("bsGroups = " + bsGroups);
        System.out.println("bsOps = " + bsOps);
        nChecked++;
        boolean isOK = true;
        bsPoints.clearAll();
        bsPoints.or(bsPoints0);
        targeted.clearAll();
        for (int i = bsPoints.nextSetBit(0); i >= 0; i = bsPoints
            .nextSetBit(i + 1)) {
          bsPoints.clear(i);
//          if (iop == 15 && i == 46)
//            System.out.println("????");
          int j = findEquiv(iop, op, i, bsPoints, pt, true);
          if (j < 0) {
              System.out.println("failed op " + iop + " for atom " + i + " " + atoms[i].name + " " + atoms[i] + " looking for " + pt + "\n" + op);
            isOK = false;
            break;
          }
          if (i != j) {
            targeted.set(j);
          }
        }
        BS myGroups = bsOpGroups[iop];
        bsOps.clear(iop);
        opsChecked.set(iop);
        if (isOK) {
          System.out.println("OK!");
          if (iop == 1)
            hasC1 = true;
          // iop was found
          targets.or(targeted);
          //System.out.println("targeted=" + targeted);
          //System.out.println("targets=" + targets);
          //reduce the number of possible groups to groups having this operation
          bsGroups.and(myGroups);
          // reduce the number of operations to check to only those NOT common to 
          // all remaining groups;
          temp1.setBits(1, OP_COUNT);
          for (int i = bsGroups.nextSetBit(0); i >= 0; i = bsGroups
              .nextSetBit(i + 1)) {
            temp1.and(bsGroupOps[i]);
          }
          uncheckedOps.or(temp1);
          bsOps.andNot(temp1);
        } else {
          // iop was not found
          // clear all groups that require this operation
          bsGroups.andNot(myGroups);
          // trim ops to only those needed for compatible groups
          // and retain only operations for groups that have this operation
          temp1.clearAll();
          for (int i = bsGroups.nextSetBit(0); i >= 0; i = bsGroups
              .nextSetBit(i + 1)) {
            temp1.or(bsGroupOps[i]);
          }
          bsOps.and(temp1);
        }
      }
      n = bsGroups.cardinality();
      if (n == 0) {
        bsGroups.set(hasC1 ? 1 : 0);
        n = 1;
        if (hasC1 && !asString) {
          uncheckedOps.clearAll();
          uncheckedOps.set(1);
          opsChecked.clearAll();
          targets.clearAll();
          bsPoints.or(bsPoints0);
        }
      }
      isg = bsGroups.nextSetBit(0);
      if (n == 1) {
        if (isg > 0) {
          opsChecked.and(bsGroupOps[isg]);
          uncheckedOps.and(bsGroupOps[isg]);
          uncheckedOps.andNot(opsChecked);
          
 uncheckedOps.or(bsGroupOps[isg]);
          uncheckedOps.clear(0);

          bsPoints.or(bsPoints0);
//          System.out.println("test1 bspoints " + bsPoints.cardinality() + " " + bsPoints);
//          System.out.println("test2 targets " + targets.cardinality() + " " + targets);
//          System.out.println("test3 uchop= " + uncheckedOps.cardinality() + " " + uncheckedOps);
//          System.out.println("test4 opsc= " + opsChecked.cardinality() + " " + opsChecked);
          bsPoints.andNot(targets);
          if (!checkBasis(uncheckedOps, bsPoints, targets)) {
            isg = 0;
          }
//          System.out.println("test2b targets " + targets.cardinality() + " " + targets);
//          System.out.println("test1b points " + bsPoints.cardinality() + " " + bsPoints);
        }
        if (isg == 0)
          targets.clearAll();
      }

    } catch (Exception e) {
      e.printStackTrace();
      bsGroups.clearAll();
    }
    System.out.println("checked " + nChecked + " operations; now " + n + " "
        + bsGroups + " " + bsOps);
    for (int i = bsGroups.nextSetBit(0); i >= 0; i = bsGroups
        .nextSetBit(i + 1)) {
      System.out.println(SpaceGroup.nameToGroup.get(groupNames[i]));
    }
    if (n != 1)
      return null;
    SpaceGroup sg = SpaceGroup.nameToGroup.get(groupNames[isg]);
    String name = sg.asString();
    uc.setSpaceGroupName(name);
    BS basis = BSUtil.copy(
        //isg == 0 ? withinCell : 
          bsAtoms);
    for (int i = targets.nextSetBit(0); i >= 0; i = targets.nextSetBit(i+ 1))
      basis.clear(atoms[i].index);
    int nb = basis.cardinality();
    String msg = name + "\nbasis is " + nb + " atom" + (nb == 1 ? "" : "s") + ": " + basis;
    System.out.println(msg);
    if (asString)
      return msg;
    @SuppressWarnings("unchecked")
    Map<String, Object> map = (Map<String, Object>) sg.dumpInfoObj();
    System.out.println("unitcell is " + uc.getUnitCellInfo(true));
    BS bs1 = BS.copy(bsPoints0);
    bs1.andNot(targets);
    dumpBasis(bsGroupOps[isg], bs1, bsPoints0);
    map.put("name", name);
    map.put("basis", basis);
    map.put("supercell", scaling);
    oabc[1].scale(scaling.x);
    oabc[2].scale(scaling.y);
    oabc[3].scale(scaling.z);
    map.put("unitcell", oabc);
    return map;
  }

  private void removeDuplicates(BS bs) {
    for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
      SGAtom a = atoms[i];
      for (int j = bs.nextSetBit(0); j < i; j = bs.nextSetBit(j + 1)) {
        SGAtom b = atoms[j];
        if (a.typeAndOcc == b.typeAndOcc && a.distanceSquared(b) < JC.UC_TOLERANCE2) {
          bs.clear(i);
          break;
        }
      }
      
    }
  }

  @SuppressWarnings("unused")
  private void dumpBasis(BS ops, BS bs1, BS bsPoints) {
//    System.out.println("----");
//    for (int i = bs1.nextSetBit(0); i >= 0; i = bs1.nextSetBit(i + 1)) {
//      for (int iop = ops.nextSetBit(1); iop >= 0; iop = ops
//          .nextSetBit(iop + 1)) {
//        SymmetryOperation op = getOp(iop);
//        System.out.print(i + ":" + iop + " = ");
//        int i0 = i;
//        int j = -1;
//        while (j != i) {
//          j = findEquiv(-1, op, i0, bsPoints, pt, false);
//          if (j == i)
//            break;
//          System.out.print(" " + j);
//          i0 = j;
//        }
//        System.out.print("\n");
//      }
//    }
//    System.out.println("----");
  }

  private boolean checkBasis(BS uncheckedOps, BS bsPoints, BS targets) {
	  
    int n = uncheckedOps.cardinality();
    if (n == 0)
      return true;
    BS bs = new BS();
    bs.or(bsPoints);
    System.out.println("finishing check for basis for " + n + " operations");
    for (int iop = uncheckedOps.nextSetBit(0); iop >= 0; iop = uncheckedOps
        .nextSetBit(iop + 1)) {
//      if (bs.cardinality() == 0)
//        return;
// added
      bs.or(bsPoints);
      SymmetryOperation op = getOp(iop);
      for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
        int j = findEquiv(-1, op, i, bs, pt, false);
        if (j < 0)
          return false;
        if (i != j) {
          j = Math.max(i, j);
          targets.set(j);
          bs.clear(j);
        }
      }
    }
    return true;
  }
//  private boolean checkBasis(BS uncheckedOps, BS bs, BS targets) {
//    int n = uncheckedOps.cardinality();
//    if (n == 0)
//      return true;
//    int[] basis = new int[nAtoms];
//    for (int i = nAtoms; --i >= 0;)
//      basis[i] = Integer.MAX_VALUE;
//    System.out.println("finishing check for basis for " + n + " operations");
//    bs.setBits(0, nAtoms);
//    System.out.println(
//        "checkBasis " + bs.cardinality() + " " + targets.cardinality() + " " + bs);
//    for (int iop = uncheckedOps.nextSetBit(1); iop >= 0; iop = uncheckedOps
//        .nextSetBit(iop + 1)) {
//      SymmetryOperation op = getOp(iop);
//      for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
//        int j = findEquiv(-1, op, i, bs, pt, false);
//        if (j < 0)
//          return false;
//        if (i == 12 || j == 12)
//          System.out.println("??" + op + iop + " " + i + " " + j + atoms[i] + atoms[j]);
//          if (i < basis[j]) {
//            basis[j] = i;
//          } else if (j < basis[i]) {
//              basis[i] = j;
//          }
//      }
//    }
//    BS bsnew = new BS();
//    for (int i = 0; i < nAtoms; i++) {
//      if (basis[i] >= i)
//        bsnew.set(i);
//    }
//    bs.and(bsnew);
//    bs.andNot(targets);
//    targets.setBits(0, nAtoms);
//    targets.andNot(bs);
//    System.out.println(
//        "checkBasis " + bs.cardinality() + " " + targets.cardinality() + " " + bs);
//    return true;
//  }


  /**
   * Remove possibilities based on unit cells. No attempt is made to permute
   * axes. Rhombohedral groups are never included.
   * 
   * orthorhombic = 16 - 74 (a, b, c, 90, 90, 90)
   * 
   * tetragonal = 75 - 142 (a, a, c, 90, 90, 90)
   * 
   * trigonal = 143 - 194 (a, a, c, 90, 90, 120)
   * 
   * cubic = 195 - 230 (a, a, a, 90, 90, 90)
   * 
   * 
   * @param bsGroups
   * @param uc
   */
  private void filterGroups(BS bsGroups, SymmetryInterface uc) {
    float[] params = uc.getUnitCellParams();
    boolean isOrtho = false, isTet = false, isTri = false,
        isRhombo = false, isCubic = false;
    boolean absame = approx0(params[0]-params[1]);
    boolean bcsame = approx0(params[1] - params[2]);
    if (params[3] == 90) { // alpha
      if (params[4] == 90) { // beta
        // really?? 
        if (absame && params[0] != params[1]) 
          System.out.println("OHOH"); 
        // stoltzite.cif?? 
        //        _cell_length_a             5.4450502
        //        _cell_length_b             5.44503
        //         _space_group_IT_number     88

        // Tausonite.cif (Ok)
        //       _cell_length_a             5.4784
        //       _cell_length_b             5.4791
        //       _space_group_IT_number     62


        isTri = (absame && approx000(params[5] - 120));
        if (params[5] == 90) { // gamma
          isCubic = (absame && params[1] == params[2]);
          isTet = (!isCubic && absame);
          isOrtho = (!isCubic && !isTet);
        }
      }
    } else if (absame && bcsame
        && params[3] == params[4]  && params[4] == params[5]) {
      isRhombo = true;
    }
    bsGroups.setBits(0, 2);
    int i0 = 2, i = 2;
    while (true) {
      i = scanTo(i, "16");
      if (!isOrtho && !isTet && !isTri && !isRhombo && !isCubic)
        break;

      i = scanTo(i, "75");
      if (!isTet && !isTri && !isRhombo && !isCubic)
        break;

      i = scanTo(i, "143");
      if (!isTri && !isRhombo && !isCubic)
        break;

      i0 = i;
      for (;; i++) {
        String g = groupNames[i];
        if (g.indexOf(":r") >= 0) {
          if (!isRhombo)
            continue;
          bsGroups.set(i);
        }
        if (g.startsWith("195")) {
          if (isRhombo)
            return;
          break;
        }
      }
      if (!isCubic)
        break;
      bsGroups.setBits(2, i0);
      i0 = i;
      i = GROUP_COUNT;
      break;
    }
    bsGroups.setBits(i0, i);
  }

  private static int scanTo(int i, String num) {
    for (;; i++) {
      if (groupNames[i].startsWith(num))
        break;
    }
    return i;
  }

  private Object getGroupsWithOp(String opXYZ0) {
    BS groups = null;
    for (int i = opXYZ.length; --i >= 0;) {
      if (opXYZ[i].equals(opXYZ0)) {
        groups = bsOpGroups[i];
        System.out.println("found for " + opXYZ0 + " " + groups);
        break;
      }
    }
    if (groups != null) {
      String[] ret = new String[groups.cardinality()];
      for (int p = 0, i = groups.nextSetBit(0); i >= 0; i = groups.nextSetBit(i + 1)) {
        ret[p++] = SpaceGroup.getID(i);
      }
      return ret;
    }
    return null;
  }

//  private SymmetryInterface checkTetragonal(Viewer vwr, SymmetryInterface uc, M3 mtet) {    
//    float[] params = uc.getUnitCellParams();
//    if (!approx0(params[0] - params[1])
//        && approx0(params[1] - params[2])
//        && approx0(params[3] - 90) && approx0(params[4] - 90)
//        && approx0(params[5] - 90)) {
//      // b==c, so a->c->b->a
//      mtet.setA(new float[] {0, 1, 0, 0, 0, 1, 1, 0, 0});
//      for (int i = 0; i < nAtoms; i++) {
//        mtet.rotate(atoms[i]);
//      }
//      P3[] oabc = uc.getUnitCellVectors();
//      uc = vwr.getSymTemp().getUnitCell(new P3[] {oabc[0], oabc[2], oabc[3], oabc[1]}, false, "permuted");
//    } else if (!approx0(params[0] - params[1])
//        && approx0(params[0] - params[2])
//        && approx0(params[3] - 90) && approx0(params[4] - 90)
//        && approx0(params[5] - 90)) {
//      // a==c, so b->c->a->b
//      mtet.setA(new float[] {1, 0, 0, 0, 0, 1, 0, 1, 0});
//      for (int i = 0; i < nAtoms; i++) {
//        mtet.rotate(atoms[i]);
//      }
//      P3[] oabc = uc.getUnitCellVectors();
//      uc = vwr.getSymTemp().getUnitCell(new P3[] {oabc[0], oabc[1], oabc[3], oabc[2]}, false, "permuted");
//    }
//    return uc;
//  }

  P3 toFractional(Atom a, SymmetryInterface uc) {
    pt.setT(a);
    uc.toFractional(pt, false);
    return pt;
  }

  private static SymmetryOperation getOp(int iop) {
    SymmetryOperation op = ops[iop];
    if (op == null) {
      ops[iop] = op = new SymmetryOperation(null, iop, false);
      op.setMatrixFromXYZ(opXYZ[iop], 0, false);
      op.doFinalize();
    }
    return op;
  }

//  /**
//   * Check for the same number of base (coord 0) atoms is the same as the number
//   * of packed atoms. If that is not the case, we have P1 symmetry, and we just
//   * clear all points.
//   * 
//   * @param bsPoints
//   *        set to empty if there is a problem
//   * @param extraAtoms
//   * @param packedAtoms
//   *        to be filled
//   * @param abc
//   */
//  private void checkPackingAtoms(BS bsPoints, BS extraAtoms, int abc,
//                                 BS packedAtoms) {
//    int nextra = extraAtoms.cardinality();
//    if (nextra == 0)
//      return;
//    int nbase = 0;
//    int npacked = 0;
//    float f = 0;
//    for (int i = bsPoints.nextSetBit(0); i >= 0; i = bsPoints
//        .nextSetBit(i + 1)) {
//      SGAtom a = atoms[i];
//      switch (abc) {
//      case 1:
//        f = a.x;
//        break;
//      case 2:
//        f = a.y;
//        break;
//      case 3:
//        f = a.z;
//        break;
//      }
//      if (approx01(f)) {
//        nbase++;
//      } else if (approx01(f - 1)) {
//        npacked++;
//        packedAtoms.set(i);
//      }
//    }
//    if (nbase != npacked) {
//      bsPoints.clearAll();
//      extraAtoms.clearAll();
//    }
//  }

  /**
   * Look for a supercell and adjust lattice down if necessary.

   * @param vwr 
   * @param uc
   * @param bsPoints
   * @param abc
   *        1==a, 2==b, 3==c
   * @param scaling
   *        set to [na, nb, nc]
   * @return revised unit cell
   */
  public SymmetryInterface checkSupercell(Viewer vwr, SymmetryInterface uc,
                                          BS bsPoints, int abc, P3 scaling) {
    if (bsPoints.cardinality() == 0)
      return uc;
    int minF = Integer.MAX_VALUE, maxF = Integer.MIN_VALUE;
    int[] counts = new int[MAX_COUNT + 1];
    for (int i = bsPoints.nextSetBit(0); i >= 0; i = bsPoints
        .nextSetBit(i + 1)) {
      SGAtom a = atoms[i];
      int type = a.typeAndOcc;
      //String name = a.name;
      SGAtom b;
      float f;
      for (int j = bsPoints.nextSetBit(0); j >= 0; j = bsPoints
          .nextSetBit(j + 1)) {
        if (j == i || (b = atoms[j]).typeAndOcc != type)
          continue;
        pt.sub2(b, a);
        switch (abc) {
        case 1:
          if (approx0(f = pt.x) || !approx0(pt.y) || !approx0(pt.z))
            continue;
          break;
        case 2:
          if (approx0(f = pt.y) || !approx0(pt.x) || !approx0(pt.z))
            continue;
          break;
        default:
        case 3:
          if (approx0(f = pt.z) || !approx0(pt.x) || !approx0(pt.y))
            continue;
          break;
        }
        int n = approxInt(1 / f);
        // must be positive
        //System.out.println(n + " " + f + " " + abc + " " + pt + " " + a + " " + b + " " + nAtoms + " " + n + " " + counts[n]);
        if (n == 0 || nAtoms / n != 1f * nAtoms / n || n > MAX_COUNT)
          continue;
        //System.out.println(abc + " " + pt + " " + a + " " + b + " " + nAtoms + " " + n + " " + counts[n]);
        if (n > maxF)
          maxF = n;
        if (n < minF)
          minF = n;
        counts[n]++;
      }
    }
    int n = maxF;
    while (n >= minF) {
      if (counts[n] > 0 && counts[n] == (n - 1) * nAtoms / n) {
        break;
      }
      --n;
    }
    if (n < minF)
      return uc;
    // we have the smallest unit in this direction
    P3[] oabc = uc.getUnitCellVectors();
    oabc[abc].scale(1f / n);
    switch (abc) {
    case 1:
      scaling.x = n;
      break;
    case 2:
      scaling.y = n;
      break;
    case 3:
      scaling.z = n;
      break;
    }
    uc = vwr.getSymTemp().getUnitCell(oabc, false, "scaled");
    // remove points not within this unitcell
    int f = 0;
    for (int i = bsPoints.nextSetBit(0); i >= 0; i = bsPoints
        .nextSetBit(i + 1)) {
      switch (abc) {
      case 1:
        f = approxInt(n * atoms[i].x);
        break;
      case 2:
        f = approxInt(n * atoms[i].y);
        break;
      case 3:
        f = approxInt(n * atoms[i].z);
        break;
      }
      if (f != 0) {
        atoms[i] = null;
        bsPoints.clear(i);
      }
    }
    nAtoms = bsPoints.cardinality();
    return uc;
  }

  private boolean approx0(float f) {
    return (Math.abs(f) < SLOP001);
  }

  private boolean approx000(float f) {
    return (Math.abs(f) < SLOP0001);
  }

  private boolean approx0014(float f) {
    return (Math.abs(f) < SLOP0014);
  }

//  private boolean approx01(float f) {
//    return (Math.abs(f) < SLOP02);
//  }
//
  private int approxInt(float finv) {
//    int i = Math.round (finv); // was 
    int i = (int) (finv + SLOP001);
    return (Math.abs(finv - i) < SLOP001 ? i : 0);
  }

  @SuppressWarnings("unused")
  private int findEquiv(int iop, SymmetryOperation op, int i, BS bsPoints,
                        P3 pt, boolean andClear) {
    SGAtom a = atoms[i];
    pt.setT(a);
    op.rotTrans(pt);
    SimpleUnitCell.unitizeDim(3, pt);
    if (pt.distanceSquared(a) == 0) {
      return i;
    }
    int testiop = -99;
    int type = a.typeAndOcc;
    String name = a.name;
    
    for (int j = nAtoms; --j >= 0;) {
      SGAtom b = atoms[j];
      if (b.typeAndOcc != type)
        continue;
      float d = b.distance(pt);
//      if (iop == 15 && j == 98 && i == 46)
//        System.out.println("???");
      if (d * d < JC.UC_TOLERANCE2 
          || (1 - d) * (1 - d) < JC.UC_TOLERANCE2
          && latticeShift(pt, b)
          ) { // this is a SQUARE
        if (andClear) {
          j = Math.max(i, j);
          if (i != j)
            bsPoints.clear(j);
        }
        return j;
      }
    }
    return -1;
  }

  /**
   * Look for true {1 0 0}, {0 1 0}, {0 0 1} difference.
   * 
   * This comes from issues with SimpleCell.unitize and our methods here, I think.
   * 
   * @param a
   * @param b
   * @return true if a lattice shift
   */
  private boolean latticeShift(P3 a, P3 b) {
    boolean is1 = (approx0014(Math.abs(a.x - b.x) - 1)
        || approx0014(Math.abs(a.y - b.y) - 1)
        || approx0014(Math.abs(a.z - b.z) - 1));
    if (is1) {
      // System.out.println("was 1 for " + a + b); 
      // Heulandite, Jalpaite, Letovicite, Merrillite, 
      // Schreyerite, Supierite, Shigaite, Tobermorite
    }
    return is1;
  }

  public static void main(String[] args) {
    if (loadData(null, new SpaceGroupFinder()))
      System.out.println("OK");
  }

  private static boolean loadData(Viewer vwr, Object me) {
    try {
      groupNames = getList(vwr, me, null, "sggroups_ordered.txt");
      GROUP_COUNT = groupNames.length;
      opXYZ = getList(vwr, me, null, "sgops_ordered.txt");
      OP_COUNT = opXYZ.length;
      String[] map = getList(vwr, me, new String[OP_COUNT], "sgmap.txt");
      bsGroupOps = new BS[GROUP_COUNT];
      bsOpGroups = new BS[OP_COUNT];
      for (int j = 0; j < GROUP_COUNT; j++)
        bsGroupOps[j] = BS.newN(OP_COUNT);
      for (int i = 0; i < OP_COUNT; i++) {
        String m = map[i];
        bsOpGroups[i] = BS.newN(GROUP_COUNT);
        for (int j = 0; j < GROUP_COUNT; j++) {
          if (m.charAt(j) == '1') {
            bsGroupOps[j].set(i);
            bsOpGroups[i].set(j);
          }
        }
      }
      ops = new SymmetryOperation[OP_COUNT];
      return true;
    } catch (Exception e) {
      e.printStackTrace();
      return false;
    } finally {
      if (rdr != null)
        try {
          rdr.close();
        } catch (IOException e) {
        }
    }
  }

  private static String[] getList(Viewer vwr, Object me, String[] list,
                                  String fileName)
      throws IOException {
    rdr = FileManager.getBufferedReaderForResource(vwr, me,
        "org/jmol/symmetry/", "sg/" + fileName);
    if (list == null) {
      Lst<String> l = new Lst<String>();
      String line;
      while ((line = rdr.readLine()) != null) {
        if (line.length() > 0) {
          l.addLast(line);
        }
      }
      l.toArray(list = new String[l.size()]);
    } else {
      for (int i = 0; i < list.length; i++)
        list[i] = rdr.readLine();
    }
    rdr.close();
    return list;
  }
  
// sg.spt - for generating the data files 
// this list was then processed by Excel pivot tables to get the binary listing
// sorted by 
//      x = spacegroup("all")
//      types = {"all":[1, 230]}
//
//      for (name in types) {
//      lat = types[name]
//
//      a = ""
//      lastname =""
//      for (var sg in x.spaceGroupInfo) {
//        if (sg.itaFull == lastname || sg.itaFull[0] == "*" || sg.ita < lat[1] || sg.ita > lat[2]) {
//          print "dup " + sg.itaFull
//          continue
//        }
//        lastname = sg.itaFull
//        var s = lastname.split(":")
//        var post = (s.length == 1 ? "" : ":" + s[2]) 
//        var pre = "\n\"" + s[1].format("%03s")+post + "\"\t\""
//        var list = sg.operationsXYZ
//        var n = sg.operationCount
//        for (var i = 1; i <= n; i++){
//          a +=  pre +  list[i] + "\""
//        }
//      }
//
//      write var a @{"c:/temp/sg_"+name}
//      }
//

  // load =ams/corkite 1 packed
  // 160:h HM:R 3 m:h Hall:R 3 -2"

  // load =ams/cordierite 1 packed
  // 66 HM:C c c m Hall:-C 2 2c
  
  
}
