classdef SoCDDR4TransmitRepeatHelper < handle
    %BASE Summary of this class goes here
    %   Detailed explanation goes here

    properties
        AXISFrameSize
        SamplesPerClock
        WrAddrAXIM
        WrAddrSize % number of bytes per word read/write for address
        FrameBody % array that holds equal sized frames
        FrameLast % last frame that holds data that is less than frame-size
        axis_framesize = 1024;% specified frame-size
    end

    properties(Constant)
        SampleWidth = 16;
        DDR4_MAX_ADDR = 2^32;
    end

    properties(SetAccess = protected)
        NumFrames = 1; % report total number of frames
        HasTermFrame = false; % indicate if there is a last frame that is not the full frame length
    end

    methods
        function obj = SoCDDR4TransmitRepeatHelper(varargin)
            obj.AXISFrameSize = 100; % The DMA frame length which will be specified in IIO
            obj.SamplesPerClock = 4;
            obj.WrAddrSize = obj.SampleWidth*obj.SamplesPerClock / 8 ; % in bytes
            obj.WrAddrAXIM = 0;
            %             if isempty(which('axis_multiframe.BitPacker_mex'))
            %                 error('Could not find BitPacker_mex.mex* file! Please run the MATLAB Coder script to generate this MEX file and place it in +axis_multiframe/');
            %             end
        end

        function [obj, ResizedContainer] = AssembleTxFrame(obj,data)
            Tx_DDR4_NumSamples = numel(data);
            Iindx = zeros(1, Tx_DDR4_NumSamples);
            Iindx(1:2:end) = 1:4:Tx_DDR4_NumSamples*2;
            Iindx(2:2:end) = 2:4:Tx_DDR4_NumSamples*2;
            Qindx = zeros(1, Tx_DDR4_NumSamples);
            Qindx(1:2:end) = 3:4:Tx_DDR4_NumSamples*2;
            Qindx(2:2:end) = 4:4:Tx_DDR4_NumSamples*2;
            dataIn = zeros(1, Tx_DDR4_NumSamples*2);
            dataIn(Iindx) = real(data);
            dataIn(Qindx) = imag(data);
%             dataIn = dataIn*100;
            
            ResizedContainer = obj.CompactInputArray(dataIn);
            TxFrames = obj.AttachHeaderPackets(ResizedContainer);
            obj.updateFrame(TxFrames);
        end

        function [FullDataArray, Valid, EoF]= FormatForSim(obj,TxFramesObj,varargin)
            % varargin
            % 1 - add gaps in-between each frame
            %

            %% Define main data array
            [NSamples, NFrames]= size(TxFramesObj.FrameBody);
            LastFrame = cast(zeros(obj.AXISFrameSize,1),'like',TxFramesObj.FrameLast);
            DataFrame = cast(zeros(obj.AXISFrameSize,NFrames),'like',TxFramesObj.FrameBody);
            LastFrame(1: length(TxFramesObj.FrameLast)) = TxFramesObj.FrameLast(:);
            TLastArr = boolean(DataFrame);
            TValidArr = boolean(DataFrame);
            TValidArr(:) = true;
            TLastArr(end,:) = true;


            if TxFramesObj.HasTermFrame == 0 && TxFramesObj.NumFrames == 1
                % Handle case where we only have one frame
                DataFrame(1:length(TxFramesObj.FrameBody)) = TxFramesObj.FrameBody;
            else
                DataFrame = TxFramesObj.FrameBody;
            end

            if ~isempty(varargin)
                delayGap = varargin{1};
                %Insert gaps here:
                if TxFramesObj.HasTermFrame == 0 && TxFramesObj.NumFrames == 1
                    % Handle case where we only have one frame
                    GapArr = zeros(delayGap,1);
                else
                    GapArr = zeros(delayGap,TxFramesObj.NumFrames-1);
                end
                TLastArr = [TLastArr ; GapArr ];
                TValidArr = [TValidArr ; GapArr ];
                DataFrame = [DataFrame ;GapArr ];
            end

            %% Define end of frame
            if (TxFramesObj.HasTermFrame == 0) || TxFramesObj.NumFrames == 1
                % If only 1 frame
                FullDataArray = DataFrame(:);
                EoF = TLastArr(:);
            else
                % Multi-frame case
                FullDataArray = [DataFrame(:); LastFrame]; %concat body-frame with last frame
                EoF = [TLastArr(:); LastFrame*0];          %concat EOF frame
                EoF(end) = true;
                TValidArr = [TValidArr(:); ones(length(LastFrame),1)]; %concat valid
            end

            Valid = TValidArr;
        end

    end

    methods(Access = public,Hidden)

        function [AXIS_Frame]= AttachHeaderPackets(obj,dataInput)
            PayloadSize = numel(dataInput);
            if PayloadSize <= (obj.AXISFrameSize-1)  % obj.AXISFrameSize - 1 = room for header packet

                HEADER_TDATA  = obj.CalculateHeader(0,PayloadSize);

                TDATA = [HEADER_TDATA ;uint64(dataInput(:))]; %attach header packet to front of payload
                AXIS_Frame = { TDATA []};


            else
                % Use Framesize - 1 is because we need to make room for the
                % header packet
                idx_rem = mod(PayloadSize,obj.AXISFrameSize-1);
                FramePrimary = reshape(dataInput(1:end-idx_rem),obj.AXISFrameSize-1,[]); %rows = samples, col = frames
                FrameLast = dataInput(end-idx_rem+1:end);  % represents last frame with only partial data
                [~,NumPayloads] = size(FramePrimary);
                % Generate header packets as an array
                % the numPrimaryFrames + 1 is because we include the
                % last frame
                % The WrAddrArray will offset each frame appropiately in
                % DDR to line up contigiously
                ByteMultiply = obj.WrAddrSize;
                WrAddrArray  = ByteMultiply*[0:(obj.AXISFrameSize-1):(obj.AXISFrameSize-1)*(NumPayloads-1)];
                HeaderPacketPrimary = arrayfun(@(x)obj.CalculateHeader(x,obj.AXISFrameSize-1),WrAddrArray);

                HeaderPacketLast = obj.CalculateHeader(WrAddrArray(end) + ByteMultiply*(obj.AXISFrameSize - 1),...
                    numel(FrameLast));


                FramePrimary = [HeaderPacketPrimary; FramePrimary];
                FrameLast = [ HeaderPacketLast; FrameLast(:)];

                AXIS_Frame = {FramePrimary FrameLast};
            end

        end


        function HEADER_TDATA = CalculateHeader(obj,WrAddr,PayloadSize)
            % perform checks here
            % ADDRESS + FRAME_LEN < MAX DDR4 size
            %
            if ((WrAddr + PayloadSize) > obj.DDR4_MAX_ADDR)
                error('Specified write address exceeds DDR4 memory space');
            end
            HdrPkt1_wrd = fi(15,0,4,0);
            HdrPkt2_wrd = fi(14,0,4,0);
            OffsetAddr_wrd = fi(obj.WrAddrAXIM + WrAddr,0,32,0);
            FrameLen_wrd = fi(PayloadSize,0,20,0);
            HEADER_TDATA = uint64(bitconcat(FrameLen_wrd,...
                OffsetAddr_wrd,...
                HdrPkt2_wrd,...
                HdrPkt1_wrd));
        end

        function decodeHeaderPacket(obj)
        end

        function ResizedContainer = CompactInputArray(obj,InputData)
            %% reshape into 64-bit values

            DataLen = length(InputData);
            idx_rem = mod(DataLen,obj.SamplesPerClock);
            if idx_rem ~= 0
                warning(['Removing %d samples in order to shape data length to ',...
                    'AXI4-Stream frame-size. Ensure specified frame-length is a multiple of %d'],...
                    idx_rem,...
                    obj.SamplesPerClock);
            end
            DataArrayPrimary = reshape(InputData(1:end-idx_rem),obj.SamplesPerClock,[]); %rows = samples, col = frames

            [~, NumFramesContainer] = size(DataArrayPrimary);


            AXIS_DataType = getDataType(obj);
            if ischar(AXIS_DataType)
                %                 ResizedContainer = arrayfun(@(x)typecast(int16(DataArrayPrimary(:,x)),AXIS_DataType),1:NumFramesContainer);
                ResizedContainer = socWaveformTxBitPackerMex(double(DataArrayPrimary),NumFramesContainer);
            elseif isa(AXIS_DataType,'embedded.numerictype')
                error('Not supported yet');
            end
        end

        function DataType = getDataType(obj)
            switch(obj.SamplesPerClock)
                case({4,2})
                    switch(obj.SamplesPerClock)
                        case(4)
                            DataType = 'uint64';
                        case(2)
                            DataType = 'uint32';
                    end
                case(8)
                    DataType = numerictype(0,128,0);
                otherwise
                    error('Unsupported data type');
            end
        end

        function updateFrame(obj,Frames)
            % Populate object
            % Arguments: FrameBody - array of equal sized frames
            %            varargin - optional last frame

            % Validate argument
            if numel(Frames) ~= 2 && ~isa(Frames,'cell') && ismatrix(Frames{1})
                error('Incorrect input data specified! Expected a cell array of size 2 containing frames!');
            end


            obj.FrameBody = Frames{1};

            % validate values
            % Expected format - rows = samples, columns = frames
            [frameSize,frameCount] = size(obj.FrameBody);

            obj.axis_framesize = frameSize;
            obj.NumFrames = frameCount;

            if ~isempty(Frames{2})
                obj.FrameLast = Frames{2};
                obj.HasTermFrame = true;
                obj.NumFrames = obj.NumFrames + 1; % including last frame
            else
                obj.FrameLast = [];
                obj.HasTermFrame = false;
            end

        end
    end
end

