function [sigma3n_twin_variants, twin_variants_label, disorient_and_indcs_struct] = gtTwinCalcSigma3nVariantsQuat(N, space_group, symm, latticepar)
% GTTWINCALCSIGMA3NVARIANTSQUAT  Function to calculate the sigma 3n twin
%                                variants and their labels in TRD
%
%     [sigma3n_twin_variants, twin_variants_label] = gtTwinCalcSigma3nVariantsQuat(N)
%     ----------------------------------------------------------------------------
%     INPUT:
%       N                     = <double>   Order of sigma value.
%
%     OUTPUT:
%       sigma3n_twin_variants = <doubel>   The quaternions of the sigma 3n
%                                          twin variants. 4x2(3^N - 1)
%                                          array.
%       twin_variants_label   = <doubel>   The labels of twin variants.
%                                          Nx2(3^N - 1) array.
%       disorient_and_indcs_struct = <struct> Containing rotation angles
%                                             and orientation families of
%                                             rotation axes and the
%                                             corresponding column indices 
%                                             in "sigma3n_twin_variants" 
%                                             and "twin_twin_variants_label"
%
%     Verison 001 23-02-2021 by ZLiu, zheheng.liu@esrf.fr
%
%     Reference
%     [1] Bryan W Reed, Roger W Minich, Robert E Rudd, and Mukul Kumar. The structure of the cubic coincident site lattice rotation group. Acta Crystallographica Section A: Foundations of Crystallography,60(3):263–277, 2004.
%     [2] Heinz, A., & Neumann, P. (1991). Representation of orientation and disorientation data for cubic, hexagonal, tetragonal and orthorhombic crystals. Acta crystallographica section a: foundations of crystallography, 47(6), 780-789.
    if ~nargin || isempty(N) || N < 1
        N = 1;
    end
    if nargin < 2
        space_group = 225;
    end
    if nargin < 3
        symm = [];
    end
    fcc_space_group = [196 202 203 209 210 216 219 225 226 227 228];
    bct_space_group = 123;
    flag_isfcc = any(space_group == fcc_space_group);
    flag_isbct = any(space_group == bct_space_group);
    if flag_isfcc || flag_isbct || true
        nof_s3 = 4;
    end
    % total number of twin variants from sigma 3 to sigma 3^N equals to 4 + 4*3 + ... +4*3^(N-1) = 2(3^N - 1)
    nof_s3n = nof_s3 / (nof_s3 - 2) * ((nof_s3 - 1) ^ N - 1);
    sigma3n_twin_variants = zeros(4, nof_s3n);
    twin_variants_label = uint8(zeros(N, nof_s3n)); % label the path of sigma 3 twin variants
    % sigma 3 twin variants. Use rotating 180 degrees to substitute rotating
    % 60 degrees.
    if flag_isfcc || flag_isbct
        twin_variants_label(1, 1:4) = [1 2 3 4]; % labels of sigma 3 twin variants
        if flag_isfcc
            sigma3n_twin_variants(:, 1:4) = [[0, 1, 1, 1]', [0, 1, 1, -1]', [0, 1, -1, 1]', [0, -1, 1, 1]'] / sqrt(3); % Reed et al. (2004)
            indcs_twin_variants = [2, 3, 4; 1, 3, 4; 1, 2, 4; 1, 2, 3].'; % indices of next three possible sigma 3 twin variants for a given twin variant
        else
            misor_tri = [latticepar(1), latticepar(3)] / norm([latticepar(1), latticepar(3)]);
            sigma3n_twin_variants(:, 1:4) = [misor_tri(2) * ones(1, nof_s3); ...
                [1, -1, 0, 0; 0, 0, 1, -1] * misor_tri(1); ...
                zeros(1, nof_s3)];
            indcs_twin_variants = [1, 3, 4; 2, 3, 4; 1, 2, 3; 1, 2, 4].'; % indices of next three possible sigma 3 twin variants for a given twin variant
        end
    end
    % loop for n
    sigma3n_inds = 1:nof_s3; % sigma 3
    for n = 2:N
        % loop for the twin variants of the last sigma value to calculate the twin variants of current sigma value.
        % e.g. if the last sigma value is 3^(n-1), there are 4*3^(n-2) twin
        % variants of last sigma value. Since the number of twin variants of all the sigma values before last sigma value is
        % 2(3^(n-2) - 1), the indices of twin variants of last sigma value are from 2(3^(n-2) - 1) + 1 to
        % 2(3^(n-2) - 1) + 4*3^(n-2), i.e., from 2*3^(n-2) - 1 to 2(3^(n-1) - 1)
        % Start to calculate the twin variants of current sigma value based on
        % each twin variant of last sigma value.
        % The indices of next three possible sigma 3 twin variants of
        % this twin variant.
        sub_twin_variants_indcs = indcs_twin_variants(:, twin_variants_label(n - 1, sigma3n_inds)); % sigma 3^(n-1)
        sub_twin_variants_indcs = sub_twin_variants_indcs(:);
        sigma3n_ind_dup_3x = ones(nof_s3 - 1, 1) * sigma3n_inds;
        sigma3n_ind_dup_3x = sigma3n_ind_dup_3x(:);
        % The indices of twin variants of current sigma value are from
        % 2*3^(n-1) - 1 to 2(3^n - 1) and last_order_sigma3n_inds(2) is
        % 2*3^(n-2). 
        % Then, calculate the corresponding three twin variants and
        % store them at corresponding indices.
        nof_sigma3n_inds = length(sigma3n_inds);
        sigma3n_inds_1 = (sigma3n_inds(nof_sigma3n_inds) + 1):(sigma3n_inds(nof_sigma3n_inds) + nof_sigma3n_inds * (nof_s3 - 1)); % sigma 3^n
        sigma3n_twin_variants(:, sigma3n_inds_1) = gtMathsQuatProduct(... % q_(3^n) = q_(3^(n-1)) * q_3
            sigma3n_twin_variants(:, sigma3n_ind_dup_3x), ...
            sigma3n_twin_variants(:, sub_twin_variants_indcs));
        % Copy the old path and add the new path.
        twin_variants_label(1:n, sigma3n_inds_1) = ...
            [twin_variants_label(1 : n - 1, sigma3n_ind_dup_3x); ...
            twin_variants_label(1, sub_twin_variants_indcs)];
        sigma3n_inds = sigma3n_inds_1;
    end
    
    % Rotation angles and orientation families of rotation axes of twin
    % variants
    if nargout > 2
        disorient_and_indcs_struct = struct(...
            'rot_ang', [], ...
            'rot_axis', [], ...
            'indices', []);
        nof_TwinVar = size(sigma3n_twin_variants, 2);
        [dis_ang_TwinVar, dis_axis_TwinVar] = gtDisorientation(...
            [ones(1, nof_TwinVar); zeros(3, nof_TwinVar)], ...
            sigma3n_twin_variants, symm, 'input', 'quat', 'constrain_to_sst', 'true', 'sort', 'descend', 'mode', 'active');
        disorient_quat = [cosd(dis_ang_TwinVar(:)'/2); bsxfun(@times, sind(dis_ang_TwinVar(:)'/2), dis_axis_TwinVar')];
        tol = 1e-2;
        indcs_TwinVar = 1:nof_TwinVar;
        nof_struct = 0;
        while ~isempty(indcs_TwinVar)
            nof_struct = nof_struct + 1;
            disorient_and_indcs_struct(nof_struct).rot_ang = dis_ang_TwinVar(indcs_TwinVar(1));
            disorient_and_indcs_struct(nof_struct).rot_axis = dis_axis_TwinVar(indcs_TwinVar(1), :);
            if numel(indcs_TwinVar) > 1
                dev_ori = gtDisorientation(...
                    disorient_quat(:, indcs_TwinVar(1)) * ones(1, numel(indcs_TwinVar) - 1), ...
                    disorient_quat(:, indcs_TwinVar(2:end)), symm, 'input', 'quat', 'mode', 'active');
                tmp_indcs = [true; abs(dev_ori) < tol];
            else
                tmp_indcs = true;
            end
            disorient_and_indcs_struct(nof_struct).indices = indcs_TwinVar(tmp_indcs);
            indcs_TwinVar = indcs_TwinVar(~tmp_indcs);
        end
    end
end