function nii = jnii2nii(jnii, varargin)
%
%    nii=jnii2nii(jnii)
%       or
%    nii=jnii2nii(jniifile)
%    jnii2nii(jniifile, niifile)
%
%    Covert a JNIfTI file or data structure to a NIfTI-1/2 structure or file
%
%    This function is compatible with both MATLAB and GNU Octave.
%    It accepts .jnii and .bnii input files
%
%    author: Qianqian Fang (q.fang <at> neu.edu)
%
%    input:
%        jnii: a JNIfTI data structure (a struct with NIFTIHeader and NIFTIData fields);
%              if jnii is a string, it represents a JNIfTI file (.jnii/.bnii)
%        niifile: if the 2nd parameter is given as a file name, the converted nifti data
%              will be save as a nii file with filename specified by niifile.
%              if the filename in niifile contains .gz, the file will be compressed using
%              the zmat toolbox.
%
%    output:
%        nii: is the converted nifti-1/2 data structure, it contains the below subfields
%          nii.img: the data volume read from the nii file
%          nii.hdr: extended raw file header, a structure that is byte-wise compatible with a
%                   nifti-1 - in this case, typecast(nii.hdr,'uint8') must be 348+4=352 bytes,
%                       including the raw nifti-1 hdr header (348 bytes) plus the 4-byte
%                       extension flags), or
%                   nifti-2 - in this case, typecast(nii.hdr,'uint8') must be 540+4=544 bytes,
%                       including the raw nifti-2 hdr header (540 bytes) plus the 4-byte
%                       extension flags)
%              if one run nii.hdr.extension=[]; the resulting struct is 348/540-byte in length
%              nii.hdr key subfileds include
%
%              sizeof_hdr: must be 348 (for NIFTI-1) or 540 (for NIFTI-2)
%              dim: short array, dim(2: dim(1)+1) defines the array size
%              datatype: the type of data stored in each voxel
%              bitpix: total bits per voxel
%              magic: must be 'ni1\0' or 'n+1\0' for NIFTI-1 data, and 'ni2\0' or 'n+2\0' for NIFTI-2 data
%
%              For the detailed nii header, please see
%              https://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1.h
%
%    dependency:
%
%          To load a JNIfTI file with compression or niifile ends with (.nii.gz/.hdr.gz/.img.gz),
%          one must install the ZMat Toolbox (http://github.com/NeuroJSON/zmat) and
%          JSONLab Toolbox (http://github.com/NeuroJSON/jsonlab);
%
%    this file is part of JNIfTI specification: https://github.com/NeuroJSON/jnifti
%
%    License: Apache 2.0, see https://github.com/NeuroJSON/jnifti for details
%

if (nargin <= 0)
    help jnii2nii;
    return
end

if (~isstruct(jnii))
    jnii = loadjnifti(jnii);
end

if (~(isfield(jnii, 'NIFTIHeader') && isfield(jnii, 'NIFTIData')))
    error('input must be a valid JNIfTI structure (needs both NIFTIHeader and NIFTIData subfields)');
end

niiformat = 'nifti1';

if ((isfield(jnii.NIFTIHeader, 'NIIFormat') && ismember(jnii.NIFTIHeader.NIIFormat(1:3), {'ni2', 'n+2'})) || max(jnii.NIFTIHeader.Dim) >= 2^32)
    niiformat = 'nifti2';
end

nii.hdr = nifticreate(jnii.NIFTIData, niiformat);
nii.img = jnii.NIFTIData;

if (isfield(jnii.NIFTIHeader, 'NIIHeaderSize'))
    nii.hdr.sizeof_hdr  = bytematch(jnii.NIFTIHeader, 'NIIHeaderSize', nii.hdr.sizeof_hdr);
end
if (isfield(nii.hdr, 'data_type'))
    nii.hdr.data_type   = bytematch(jnii.NIFTIHeader, 'A75DataTypeName', nii.hdr.data_type);
    nii.hdr.db_name = bytematch(jnii.NIFTIHeader, 'A75DBName', nii.hdr.db_name);
    nii.hdr.extents = bytematch(jnii.NIFTIHeader, 'A75Extends', nii.hdr.extents);
    nii.hdr.session_error   = bytematch(jnii.NIFTIHeader, 'A75SessionError', nii.hdr.session_error);
    nii.hdr.regular = bytematch(jnii.NIFTIHeader, 'A75Regular', nii.hdr.regular);
end

dim_info = bitor(uint8(jnii.NIFTIHeader.DimInfo.Freq), bitshift(uint8(jnii.NIFTIHeader.DimInfo.Phase), 3));
dim_info = bitor(dim_info, bitshift(uint8(jnii.NIFTIHeader.DimInfo.Slice), 6));
nii.hdr.dim_info = cast(dim_info, class(nii.hdr.dim_info));

nii.hdr.dim(1) = cast(length(jnii.NIFTIHeader.Dim), class(nii.hdr.dim));
nii.hdr.dim(2:1 + length(jnii.NIFTIHeader.Dim))   = bytematch(jnii.NIFTIHeader, 'Dim', nii.hdr.dim(2:1 + length(jnii.NIFTIHeader.Dim)));
nii.hdr.intent_p1   = bytematch(jnii.NIFTIHeader, 'Param1', nii.hdr.intent_p1);
nii.hdr.intent_p2   = bytematch(jnii.NIFTIHeader, 'Param2', nii.hdr.intent_p2);
nii.hdr.intent_p3   = bytematch(jnii.NIFTIHeader, 'Param3', nii.hdr.intent_p3);

if (ischar(jnii.NIFTIHeader.Intent))
    jnii.NIFTIHeader.Intent = niicodemap('intent', jnii.NIFTIHeader.Intent);
end
nii.hdr.intent_code = bytematch(jnii.NIFTIHeader, 'Intent', nii.hdr.intent_code);
if (ischar(jnii.NIFTIHeader.DataType))
    jnii.NIFTIHeader.DataType = niicodemap('datatype', jnii.NIFTIHeader.DataType);
end

nii.hdr.datatype    = bytematch(jnii.NIFTIHeader, 'DataType', nii.hdr.datatype);
nii.hdr.bitpix  = bytematch(jnii.NIFTIHeader, 'BitDepth', nii.hdr.bitpix);
nii.hdr.slice_start = bytematch(jnii.NIFTIHeader, 'FirstSliceID', nii.hdr.slice_start);
nii.hdr.pixdim(1) = cast(length(jnii.NIFTIHeader.VoxelSize), class(nii.hdr.pixdim));
nii.hdr.pixdim(2:2 + nii.hdr.dim(1) - 1)    = bytematch(jnii.NIFTIHeader, 'VoxelSize', nii.hdr.pixdim(2:2 + nii.hdr.dim(1) - 1));
nii.hdr.vox_offset  = bytematch(jnii.NIFTIHeader, 'NIIByteOffset', nii.hdr.vox_offset);
nii.hdr.scl_slope   = bytematch(jnii.NIFTIHeader, 'ScaleSlope', nii.hdr.scl_slope);
nii.hdr.scl_inter   = bytematch(jnii.NIFTIHeader, 'ScaleOffset', nii.hdr.scl_inter);
nii.hdr.slice_end   = bytematch(jnii.NIFTIHeader, 'LastSliceID', nii.hdr.slice_end);

if (ischar(jnii.NIFTIHeader.SliceType))
    jnii.NIFTIHeader.SliceType = niicodemap('slicetype', jnii.NIFTIHeader.SliceType);
end
nii.hdr.slice_code  = bytematch(jnii.NIFTIHeader, 'SliceType', nii.hdr.slice_code);
if (ischar(jnii.NIFTIHeader.Unit.L))
    jnii.NIFTIHeader.Unit.L = niicodemap('unit', jnii.NIFTIHeader.Unit.L);
end
if (ischar(jnii.NIFTIHeader.Unit.T))
    jnii.NIFTIHeader.Unit.T = niicodemap('unit', jnii.NIFTIHeader.Unit.T);
end

xyzt_units = bitor(uint8(jnii.NIFTIHeader.Unit.L), uint8(jnii.NIFTIHeader.Unit.T));
nii.hdr.xyzt_units = cast(xyzt_units, class(nii.hdr.xyzt_units));

nii.hdr.cal_max = bytematch(jnii.NIFTIHeader, 'MaxIntensity', nii.hdr.cal_max);
nii.hdr.cal_min = bytematch(jnii.NIFTIHeader, 'MinIntensity', nii.hdr.cal_min);
nii.hdr.slice_duration  = bytematch(jnii.NIFTIHeader, 'SliceTime', nii.hdr.slice_duration);
nii.hdr.toffset = bytematch(jnii.NIFTIHeader, 'TimeOffset', nii.hdr.toffset);
if (isfield(nii.hdr, 'glmax'))
    nii.hdr.glmax   = bytematch(jnii.NIFTIHeader, 'A75GlobalMax', nii.hdr.glmax);
    nii.hdr.glmin   = bytematch(jnii.NIFTIHeader, 'A75GlobalMin', nii.hdr.glmin);
end

nii.hdr.descrip = bytematch(jnii.NIFTIHeader, 'Description', nii.hdr.descrip);
nii.hdr.aux_file    = bytematch(jnii.NIFTIHeader, 'AuxFile', nii.hdr.aux_file);

nii.hdr.qform_code  = bytematch(jnii.NIFTIHeader, 'QForm', nii.hdr.qform_code);
nii.hdr.sform_code  = bytematch(jnii.NIFTIHeader, 'SForm', nii.hdr.sform_code);
nii.hdr.quatern_b   = bytematch(jnii.NIFTIHeader, 'Quatern.b', nii.hdr.quatern_b);
nii.hdr.quatern_c   = bytematch(jnii.NIFTIHeader, 'Quatern.c', nii.hdr.quatern_c);
nii.hdr.quatern_d   = bytematch(jnii.NIFTIHeader, 'Quatern.d', nii.hdr.quatern_d);
nii.hdr.qoffset_x   = bytematch(jnii.NIFTIHeader, 'QuaternOffset.x', nii.hdr.qoffset_x);
nii.hdr.qoffset_y   = bytematch(jnii.NIFTIHeader, 'QuaternOffset.y', nii.hdr.qoffset_y);
nii.hdr.qoffset_z   = bytematch(jnii.NIFTIHeader, 'QuaternOffset.z', nii.hdr.qoffset_z);
nii.hdr.srow_x  = cast(jnii.NIFTIHeader.Affine(1, :), class(nii.hdr.srow_x));
nii.hdr.srow_y  = cast(jnii.NIFTIHeader.Affine(2, :), class(nii.hdr.srow_y));
nii.hdr.srow_z  = cast(jnii.NIFTIHeader.Affine(3, :), class(nii.hdr.srow_z));

nii.hdr.intent_name = bytematch(jnii.NIFTIHeader, 'Name', nii.hdr.intent_name);
% nii.hdr.magic  =bytematch(jnii.NIFTIHeader, 'NIIFormat', nii.hdr.magic);

if (isfield(jnii.NIFTIHeader, 'NIIExtender'))
    nii.hdr.extension   = bytematch(jnii.NIFTIHeader, 'NIIExtender', nii.hdr.extension);
end
if (isfield(jnii.NIFTIHeader, 'NIIQfac_'))
    nii.hdr.pixdim(1)   = bytematch(jnii.NIFTIHeader, 'NIIQfac_', nii.hdr.pixdim(1));
end
if (isfield(jnii.NIFTIHeader, 'NIIUnused_'))
    nii.hdr.reserved    = bytematch(jnii.NIFTIHeader, 'NIIUnused_', nii.hdr.reserved);
end

if (isfield(jnii, 'NIFTIExtension') && iscell(jnii.NIFTIExtension))
    nii.extension = jnii.NIFTIExtension;
    if (nii.hdr.extension(1) ~= length(jnii.NIFTIExtension))
        nii.hdr.extension(1) = length(jnii.NIFTIExtension);
        warning('header extension count does not match the extension data, force update');
    end
end

if (nargin >= 2 && ischar(varargin{1}))
    savenifti(nii.img, varargin{1}, nii.hdr);
end

% ---------------------------------------------------------------------------

function dat = bytematch(jobj, key, orig)
dtype = class(orig);
dat = orig;
if (isfield(jobj, key))
    dat = cast(jobj.(key), dtype);
else
    dat = cast(0, dtype);
end
if (length(dat) < length(orig))
    dat(length(orig)) = cast(0, dtype);
end
dat = dat(1:length(orig));
