package ciss.phase_viewer.atomcoord.pmodel;

import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SpaceGroup {
    static final boolean DEBUG = false;

    // lattice type (IL from TSPACE)
    private enum LatticeType {
        TRIGONAL, PRIMITIVE, FACE, BODY, BASE_C, BASE_B, BASE_A
    };

    // primitive translation vector(s)
    private static Fraction Zero = new Fraction(0, 1);
    private static Fraction Half = new Fraction(1, 2);
    private static FractionVector[] vecTrigo = {
            new FractionVector(new Fraction(2, 3), new Fraction(1, 3),
                    new Fraction(1, 3)),
            new FractionVector(new Fraction(1, 3), new Fraction(2, 3),
                    new Fraction(2, 3)) };
    private static FractionVector[] vecFace = {
            new FractionVector(Zero, Half, Half),
            new FractionVector(Half, Zero, Half),
            new FractionVector(Half, Half, Zero) };
    private static FractionVector[] vecBody = { new FractionVector(Half, Half,
            Half) };
    private static FractionVector[] vecBaseC = { new FractionVector(Half, Half,
            Zero) };
    private static FractionVector[] vecBaseB = { new FractionVector(Half, Zero,
            Half) };
    private static FractionVector[] vecBaseA = { new FractionVector(Zero, Half,
            Half) };

    Generator generator;
    PointGroupOperation pointGroupOperation; // opMat[][][], table[][], inv[],
                                             // poingGroup
    FractionVector transVec[][]; // [ng0][4]
    private int isActive[];
    private LatticeType latticeType;

    public static void main(String args[]) throws PmodelException {
        SpaceGroup sg = new SpaceGroup(227, 1);
        System.out.println(sg);
    }

    SpaceGroup(PointGroupOperation.PointGroup pg,
            ArrayList<String> symmetryEquivPos) throws PmodelException {
        initialize(pg);

        String[] gene = symmetryEquivPos.toArray(new String[0]);

        // REGISTER GENERATOR(s)
        for (int i = 0; i < gene.length; ++i) {
            parseOperation(gene[i]);
        }

        if (isActive[0] == 0) {
            System.out
                    .println("Warning : No identical operator exist in the '_symmetry_equiv_pos_as_xyz' loop.");
            parseOperation("x,y,z");
        }

        // EXPAND (CHECK)
        if (expand() == true) {
            System.out
                    .println("Warning : Operations involved in the '_symmetry_equiv_pos_as_xyz' loop are not complete.");
        }
    }

    SpaceGroup(int spcGrpNum, int choice) throws PmodelException {
        generator = GeneratorList.getGenerator(spcGrpNum);
        if (DEBUG)
            System.out.println(generator);

        PointGroupOperation.PointGroup pg = PointGroupOperation
                .getPointGroup(spcGrpNum);

        initialize(pg);

        int numOfChoice = generator.getNumOfChoice();
        if (choice > numOfChoice - 1) {
            System.out.println(choice + " " + numOfChoice);
            PmodelException ex = new PmodelException();
            ex.message = "Too large choice number.";
            throw ex;
        }
        setLatticeType(generator.getHM()); // for primitive translation

        generate(choice);
        addPrimitiveTranslation();
    }

    public String toString() {
        String str = "";

        for (int i = 0; i < pointGroupOperation.getNg0(); ++i)
            for (int j = 0; j < 4; ++j) {
                if (transVec[i][j] == null)
                    continue;
                str = str + i + " " + isActive[i] + " " + transVec[i][j] + "\n";
            }
        return str;
    }

    public static int getMaxChoice(int spcGrpNum) {
        Generator g = GeneratorList.getGenerator(spcGrpNum);
        return g.getNumOfChoice();
    }

    void setGenerator(int spcGrpNum) {
        generator = GeneratorList.getGenerator(spcGrpNum);
    }

    private void initialize(PointGroupOperation.PointGroup pg) {
        switch (pg) {
        case Oh:
        case D6h:
            pointGroupOperation = new PointGroupOperation(pg);
            break;
        default:
            // ERROR
            PmodelRuntimeException ex = new PmodelRuntimeException();
            ex.message = "Illigal point group.";
            throw ex;
        }

        int maxOpe = pointGroupOperation.getNg0();

        // face-centered lattice require four translation vectors
        transVec = new FractionVector[maxOpe][4];
        isActive = new int[maxOpe];

        for (int i = 0; i < maxOpe; ++i) {
            isActive[i] = 0;
        }
    }

    private void generate(int choice) throws PmodelException {
        String[] gene = generator.getGenerator(choice);

        // IDENTICAL OPERATOR
        parseOperation("x,y,z");

        // REGISTER GENERATOR(s)
        for (int i = 0; i < gene.length; ++i) {
            parseOperation(gene[i]);
        }

        // EXPAND (OPERATOR)
        expand();
    }

    private boolean expand() {
        int ixj;
        boolean isExpanded = false, flag;

        int maxOpe = pointGroupOperation.getNg0();

        flag = true;
        while (flag) {
            flag = false;
            for (int i = 0; i < maxOpe; ++i) {
                for (int j = 0; j < maxOpe; ++j) {
                    if (isActive[i] == 0 || isActive[j] == 0)
                        continue;
                    ixj = pointGroupOperation.table[i][j];
                    if (!this.isActive(ixj)) {
                        flag = true;
                        isExpanded = true;
                        isActive[ixj] = 1;
                        // transration vector
                        transVec[ixj][0] = transVec[i][0].rotate(
                                pointGroupOperation.opMat[j]).add(
                                transVec[j][0]);
                    }
                }
            }
        }
        return isExpanded;
    }

    private void parseOperation(String str) throws PmodelException {
        int[][] opMat_ = new int[3][3];
        FractionVector transVec_ = new FractionVector();
        String[] regex = { "'(.*)'", // eliminate "'"
                "((-)?\\d/\\d)", // translation ignore preceding "+";
                                 // "((\\+|-)?\\d/\\d)"
                "((\\+|-)?[xyzXYZ])" // rotation
        };

        Pattern p1 = Pattern.compile(regex[0]);
        Pattern p2 = Pattern.compile(regex[1]);
        Pattern p3 = Pattern.compile(regex[2]);

        Matcher m1 = p1.matcher(str);

        // clear
        for (int i = 0; i < 3; ++i)
            for (int j = 0; j < 3; ++j)
                opMat_[i][j] = 0;

        String temp = "";
        // m1.reset();
        if (m1.find())
            temp = m1.group(1);
        else
            temp = str;
        if (DEBUG)
            System.out.println(temp);

        StringTokenizer st = new StringTokenizer(temp, ",");

        for (int j = 0; j < 3; ++j) { // X, Y, Z
            String strXYZ = st.nextToken();
            Matcher m2 = p2.matcher(strXYZ);
            if (m2.find()) {
                StringTokenizer st2 = new StringTokenizer(m2.group(1), "/");
                String strNume = st2.nextToken();
                String strDeno = st2.nextToken();
                int nume = Integer.parseInt(strNume); // 'srtNume' must be
                                                      // "Integer".
                int deno = Integer.parseInt(strDeno);
                if (DEBUG)
                    System.out.println(m2.group(1) + " " + nume + " " + deno);
                transVec_.element[j] = new Fraction(nume, deno);
            } else
                transVec_.element[j] = new Fraction(0, 1);

            Matcher m3 = p3.matcher(strXYZ);
            int flag = 0, delta = 9999;
            while (m3.find()) {
                boolean isMinus = false;
                String rotStr = m3.group(1);
                switch (rotStr.charAt(0)) {
                case '+':
                    if (DEBUG)
                        System.out.println("+++");
                    break;
                case '-':
                    if (DEBUG)
                        System.out.println("---");
                    isMinus = true;
                    break;
                default:
                    if (DEBUG)
                        System.out.println("NO+-");
                }
                switch (rotStr.charAt(rotStr.length() - 1)) {
                case 'x':
                case 'X':
                    if (DEBUG)
                        System.out.println("xxx");
                    delta = 1;
                    break;
                case 'y':
                case 'Y':
                    if (DEBUG)
                        System.out.println("yyy");
                    delta = -2;
                    break;
                case 'z':
                case 'Z':
                    if (DEBUG)
                        System.out.println("zzz");
                    delta = 4;
                    break;
                default:
                    PmodelException ex = new PmodelException();
                    ex.message = "ERROR : Invalid Operator(s).";
                    throw ex;
                }
                if (isMinus)
                    flag = flag - delta;
                else
                    flag = flag + delta;
            }
            if (DEBUG)
                System.out.println(flag);

            switch (flag) {
            case 1: // X
                opMat_[j][0] = 1;
                break;
            case -1: // -X
                opMat_[j][0] = -1;
                break;
            case 2: // -Y
                opMat_[j][1] = -1;
                break;
            case -2: // Y
                opMat_[j][1] = 1;
                break;
            case 3: // X-Y
                opMat_[j][0] = 1;
                opMat_[j][1] = -1;
                break;
            case -3: // -X+Y
                opMat_[j][0] = -1;
                opMat_[j][1] = 1;
                break;
            case 4: // Z
                opMat_[j][2] = 1;
                break;
            case -4: // -Z
                opMat_[j][2] = -1;
                break;
            default:
                PmodelException ex = new PmodelException();
                throw ex;
            }
        } // X, Y, Z
        if (DEBUG)
            System.out.println(transVec_);

        // ACTIVATION
        // OPERATOR SEARCH
        int num = -1;
        OPERATOR: for (int k = 0; k < pointGroupOperation.getNg0(); ++k) {
            for (int i = 0; i < 3; ++i) {
                for (int j = 0; j < 3; ++j)
                    if (pointGroupOperation.opMat[k][i][j] != opMat_[i][j])
                        continue OPERATOR;
            }
            num = k;
            break OPERATOR;
        }

        if (num == -1) { // ERROR (operator not found)
            if (DEBUG)
                for (int i = 0; i < 3; ++i) {
                    for (int j = 0; j < 3; ++j)
                        System.out.printf("%3d", opMat_[i][j]);
                    System.out.printf("\n");
                }
            PmodelException ex = new PmodelException();
            ex.message = "ERROR : Invalid Operator(s).";
            throw ex;
        }
        // REGISTER TRANSRATION VECTOR
        transVec[num][isActive[num]] = transVec_;
        // ACTIVATE ROTATION OPERATOR
        ++isActive[num];
    }

    boolean isActive(int i) {
        if (isActive[i] == 0)
            return false;
        else
            return true;
    }

    private void addPrimitiveTranslation() throws PmodelException {
        for (int j = 0; j < pointGroupOperation.getNg0(); ++j) {
            if (!isActive(j))
                continue;
            switch (latticeType) {
            case TRIGONAL:
                for (int i = 0; i < vecTrigo.length; ++i) {
                    transVec[j][i + 1] = transVec[j][0].add(vecTrigo[i]);
                    ++isActive[j];
                }
                break;
            case PRIMITIVE:
                break; // do nothing
            case FACE:
                for (int i = 0; i < vecFace.length; ++i) {
                    transVec[j][i + 1] = transVec[j][0].add(vecFace[i]);
                    ++isActive[j];
                }
                break;
            case BODY:
                transVec[j][1] = transVec[j][0].add(vecBody[0]);
                ++isActive[j];
                break;
            case BASE_C:
                transVec[j][1] = transVec[j][0].add(vecBaseC[0]);
                ++isActive[j];
                break;
            case BASE_B:
                transVec[j][1] = transVec[j][0].add(vecBaseB[0]);
                ++isActive[j];
                break;
            case BASE_A:
                transVec[j][1] = transVec[j][0].add(vecBaseC[0]);
                ++isActive[j];
                break;
            default:
                PmodelException ex = new PmodelException();
                ex.message = "Error: Illegal lattice type.";
                throw ex;
            }
        }
    }

    private void setLatticeType(String HM) throws PmodelException {
        switch (pointGroupOperation.pointGroup) {
        case D6h:
            switch (HM.charAt(0)) {
            case 'P':
                latticeType = LatticeType.PRIMITIVE;
                return;
            case 'R':
                latticeType = LatticeType.TRIGONAL;
                return;
            default: // ERROR
                PmodelException ex = new PmodelException();
                ex.message = "The first character of H-M symbol is "
                        + HM.charAt(0);
                throw ex;
            }
        case Oh:
            switch (HM.charAt(0)) {
            case 'P':
                latticeType = LatticeType.PRIMITIVE; // no primitive
                                                     // translations
                return;
            case 'F':
                latticeType = LatticeType.FACE;
                return;
            case 'I':
                latticeType = LatticeType.BODY;
                return;
            case 'C':
                latticeType = LatticeType.BASE_C;
                return;
            case 'B':
                latticeType = LatticeType.BASE_B;
                return;
            case 'A':
                latticeType = LatticeType.BASE_A;
                return;
            default: // ERROR
                PmodelException ex = new PmodelException();
                ex.message = "Error.";
                throw ex;
            }
        default: // ERROR
            PmodelRuntimeException ex = new PmodelRuntimeException();
            // ex.message = "Number of Rotation Matrices.";
            throw ex;
        }
    }
}
