%%%%%%%%%%%%%%%%%%% CLIENT %%%%%%%%%%%%%%%%%%%
% declare global variable
global Port header pass fail done release endConnection errorMsg
global hSize dataLenSize checkSumLenSize dataTypeLen intDataSize debugMode
global skt

%Port number
Port = 8888;

% Reserved strings has special meaning in protocol
header = "HEADER";
pass = "PASS";
fail = "FAIL";
done = "DONE";
release = "RELEASE";
endConnection = "EXIT";
errorMsg = "ERROR";

% Assign size to a variable
hSize = strlength(header);
dataLenSize = 5; %  Bytes
checkSumLenSize = 8; % Bytes
dataTypeLen = 1; % Bytes
intDataSize = 16;% bits

% debug mode
debugMode = 1;

% create socket
skt = tcpip('localhost', Port);

%open connection
fopen(skt);

%prompt user input
prompt = 'Enter valid asterix command:';
while(1)
    %ask user input
    payload = input(prompt,'s');
    cmd = encodePayload(payload);
    if(debugMode)
        fprintf("DATA SENT: %s\n",cmd);
    end
    fwrite(skt,cmd);
    
    %get acknowledgement
    rec = readData(skt);
    if(strcmp(rec,pass))
        if(debugMode)
            disp("information received by Latte successfully\n");
        end
        recv = getAllinfo(skt);
        if(recv)
           continue;
        else
           break;
        end
    elseif(strcmp(rec,fail))
        disp("Sanity check failed. Please resend.\n");
        continue;
    else
        if(debugMode)
            disp("Unknown information received. Client closed\n");
        end
        break;
    end
end
fclose(skt);
function typ=getAllinfo(skt)
    global debugMode errorMsg endConnection done
    while(1)
        recv = readData(skt);
        if(isstring(recv))
            if(strcmp(recv,done))
               if(debugMode)
                  disp("Execution finished. received zero\n"); 
               end
               typ = true;
               return
            elseif(strcmp(recv,errorMsg) || strcmp(recv,endConnection))
                if(debugMode)
                    disp("Got error. client closed"); 
                end
                typ = false;
                return
            else
                fprintf("Data received: %s\n",recv);
                continue
            end
        else
            fprintf("Data received: %d\n",recv);
        end
    end
end
% This function encodes payload to required form
function ret=encodePayload(msg)
    global dataLenSize checkSumLenSize header debugMode
    % get length in proper format
    length = packData(strlength(msg),dataLenSize);

    if(debugMode)
        fprintf("Length: %s\n",length); 
    end
    orgMsg = double(msg);
    checksum = packData(sum(orgMsg),checkSumLenSize);
    ret = strcat(header,length,msg,checksum);
    if(debugMode)
        fprintf("ENCODED DATA: %s\n",ret);
    end
end

%This function converts given integer to data bytes but keeping MSB of
%each byte 1, effectively each byte contains 7bit meaningful data
function ret=packData(msg,size)
    global debugMode
    result = '';
    msg = uint64(msg);
    for i = 0:(size-1)
        % get 7 LSB bits from messsage
        mask = hex2dec('7f');
        temp = bitand(msg,mask);
        
        % set MSB bit 1
        temp = bitor(temp,hex2dec('80'));
        chr = char(temp);
        result = strcat(result,chr);
        msg = bitsra(msg,7);
    end
    ret = flip(result);
    if(debugMode)
        fprintf("PACKED DATA:size=%d, value= %s\n",size,double(ret));
    end
end

function data=readData(skt)
    global hSize errorMsg header dataLenSize debugMode dataTypeLen checkSumLenSize
    %receive header
    head = fread(skt,hSize);
    head = string(char(head));
    if(strcmp(head,header))
        data = errorMsg;
        if(debugMode)
            fprintf("Header Incorrect. Received : %s\n",head);
        end
        return
    end
    
    %read expected dataSize
    dataLengthSize = fread(skt,dataLenSize);
    dataLengthSize = unpackData(dataLengthSize);
    
    %read data type
    dataType = fread(skt,dataTypeLen);
    
    %read information
    if(debugMode)
       fprintf("Data Size: %d\n",dataLengthSize);
    end
    info = fread(skt,dataLengthSize);
    
    %read checksum
    checkSum = fread(skt,checkSumLenSize);
    recCheckSum = unpackData(checkSum);
    expCheckSum = sum(info);
    
    %integer dataType
    if(dataType == 1)
        if(debugMode)
           fprintf("DataType: Integer\n");
        end
        info = extractData(info);
    end
    
    %string dataType
    if(dataType == 2)
        if(debugMode)
           fprintf("DataType: String\n");
           info = convertCharsToStrings(char(info));
           fprintf("Information received: %s\n",info);
        end
    end
    
    if(debugMode)
        fprintf("received checkSum = %d, expected checkSum =%d\n",recCheckSum,expCheckSum);
    end
    if(recCheckSum == expCheckSum)
       if(debugMode)
          disp("CheckSum correct") 
       end
       data = info;
       return
    else
        if(debugMode)
          disp("CheckSum incorrect") 
       end
       data = errorMsg;
       return
    end
end

function value=unpackData(msg)
    global debugMode
%     if(debugMode)
%        disp('UNPACK STARTED');
%     end
    temp = 0;
    size = length(msg);
    for i=1:size
        temp = temp * 128;
        temp = temp+ bitand(msg(i),hex2dec('7f'));
    end
    value = temp;
    if(debugMode)
       fprintf("After unpack Value: %d\n",value);
%        disp('UNPACK COMPLETE');
    end
end

function dat=extractData(msg)
    global intDataSize debugMode
    data=[];
    msg = flip(msg);
    left = intDataSize;
    value=uint64(0);
    size= length(msg);
    mask = (2 ^ intDataSize) - 1;
    if(debugMode)
       fprintf("Integer: RAW: ");
       disp(msg);
    end
    for i=1:size
        effData = bitsll(bitand(msg(i),hex2dec('7f')),(intDataSize - left));
        value = value + effData;
        left = left -7;
        if(left <= 0)
            oValue = typecast(bitand(value,mask),'int16');
            data = [data,oValue(1)];
            if(debugMode)
                fprintf("Data Received: %d\n",oValue(1));
            end
            value = bitsra(value,intDataSize);
            left = intDataSize + left;
        end
    end
    dat = data;
end
