//
//  ESPDatumCode.m
//  EspTouchDemo
//
//  Created by fby on 4/9/15.
//  Copyright (c) 2015 fby. All rights reserved.
//

#import "ESPDatumCode.h"
#import "ESPDataCode.h"
#import "ESP_ByteUtil.h"
#import "ESP_CRC8.h"
#import "ESP_NetUtil.h"

// define by the Esptouch protocol, all of the datum code should add EXTRA_LEN to prevent 0
#define EXTRA_LEN   40
#define EXTRA_HEAD_LEN  5

@implementation ESPDatumCode

- (id) initWithSsid: (NSData *) apSsid andApBssid: (NSData *) apBssid andApPwd: (NSData*) apPwd andInetAddrData: (NSData *) ipAddrData andIsSsidHidden: (BOOL) isSsidHidden
{
    self = [super init];
    if (self)
    {
        // Data = total len(1 byte) + apPwd len(1 byte) + SSID CRC(1 byte) +
        // BSSID CRC(1 byte) + TOTAL XOR(1 byte) + ipAddress(4 byte) + apPwd + apSsid apPwdLen <=
        // 105 at the moment
        
        // total xor
        UInt8 totalXor = 0;
        
        NSData *apPwdBytesData = apPwd;
        NSData *apSsidBytesData = apSsid;
        Byte apPwdBytes[[apPwdBytesData length]];
        Byte apSsidBytes[[apSsidBytesData length]];
        [apPwdBytesData getBytes:apPwdBytes length:[apPwdBytesData length]];
        [apSsidBytesData getBytes:apSsidBytes length:[apSsidBytesData length]];
        Byte apPwdLen = [apPwdBytesData length];
        ESP_CRC8 *crc = [[ESP_CRC8 alloc]init];
        [crc updateWithBuf:apSsidBytes Nbytes:(int)sizeof(apSsidBytes)];
        Byte apSsidCrc = [crc getValue];
        
        [crc reset];
        NSData *apBssidData = apBssid;
        int apBssidDataLen = (int)[apBssidData length];
        Byte apBssidBytes[apBssidDataLen];
        [apBssidData getBytes:apBssidBytes length:[apBssidData length]];
        [crc updateWithBuf:apBssidBytes Nbytes:apBssidDataLen];
        UInt8 apBssidCrc = [crc getValue];
        
        UInt8 apSsidLen = sizeof(apSsidBytes);
        
        // only support ipv4 at the moment
        UInt8 ipLen = [ipAddrData length];
        Byte ipAddrUint8s[ipLen];
        [ipAddrData getBytes:ipAddrUint8s length:[ipAddrData length]];
        
        UInt8 _totalLen = EXTRA_HEAD_LEN + ipLen + apPwdLen + apSsidLen;
        UInt8 totalLen = isSsidHidden ? (EXTRA_HEAD_LEN + ipLen + apPwdLen + apSsidLen):(EXTRA_HEAD_LEN + ipLen + apPwdLen);
        
        // build data codes
        _dataCodes = [[NSMutableArray alloc]initWithCapacity:totalLen + apBssidDataLen];
        
        ESPDataCode *dataCode = [[ESPDataCode alloc]initWithU8:_totalLen andIndex:0];
        [_dataCodes addObject:dataCode];
        totalXor ^= _totalLen;
        dataCode = [[ESPDataCode alloc]initWithU8:apPwdLen andIndex:1];
        [_dataCodes addObject:dataCode];
        totalXor ^= apPwdLen;
        dataCode = [[ESPDataCode alloc]initWithU8:apSsidCrc andIndex:2];
        [_dataCodes addObject:dataCode];
        totalXor ^= apSsidCrc;
        dataCode = [[ESPDataCode alloc]initWithU8:apBssidCrc andIndex:3];
        [_dataCodes addObject:dataCode];
        totalXor ^= apBssidCrc;
        // ESPDataCode 4 is nil
        for (int i = 0; i < ipLen; i++)
        {
            dataCode = [[ESPDataCode alloc]initWithU8:ipAddrUint8s[i] andIndex:i + EXTRA_HEAD_LEN];
            [_dataCodes addObject:dataCode];
            totalXor ^= ipAddrUint8s[i];
        }
        for (int i = 0; i < apPwdLen; i++)
        {
            dataCode = [[ESPDataCode alloc]initWithU8:apPwdBytes[i] andIndex:i + EXTRA_HEAD_LEN + ipLen];
            [_dataCodes addObject:dataCode];
            totalXor ^= apPwdBytes[i];
        }
        
        // totalXor will xor apSsidChars no matter whether the ssid is hidden
        for (int i = 0; i < apSsidLen; i++)
        {
            totalXor ^= apSsidBytes[i];
        }
        
        if (isSsidHidden)
        {
            for (int i = 0; i < apSsidLen; i++)
            {
                dataCode = [[ESPDataCode alloc]initWithU8:apSsidBytes[i] andIndex:i + EXTRA_HEAD_LEN + ipLen + apPwdLen];
                [_dataCodes addObject:dataCode];
            }
        }
        
        // add total xor last
        dataCode = [[ESPDataCode alloc]initWithU8:totalXor andIndex:4];
        [_dataCodes insertObject:dataCode atIndex:4];
        
        // add bssid
        NSUInteger bssidInsertIndex = EXTRA_HEAD_LEN;
        for (int i = 0; i < apBssidDataLen; i++) {
            int index = totalLen + i;
            Byte b = apBssidBytes[i];
            ESPDataCode *dc = [[ESPDataCode alloc] initWithU8:b andIndex:index];
            if (bssidInsertIndex >= _dataCodes.count) {
                [_dataCodes addObject:dc];
            } else {
                [_dataCodes insertObject:dc atIndex:bssidInsertIndex];
            }
            bssidInsertIndex += 4;
        }
    }
    return self;
}

- (NSData *) getBytes
{
    Byte datumCode[[_dataCodes count] * DATA_CODE_LEN];
    for (int i = 0; i < [_dataCodes count]; i++)
    {
        ESPDataCode *dataCode = [_dataCodes objectAtIndex:i];
        NSData *bytesData = [dataCode getBytes];
        Byte bytes[DATA_CODE_LEN];
        [bytesData getBytes:bytes length:DATA_CODE_LEN];
        void *dest = datumCode + i * DATA_CODE_LEN * sizeof(Byte);
        void *src = bytes;
        memcpy(dest, src, DATA_CODE_LEN);
    }
    return [[NSData alloc]initWithBytes:datumCode length:sizeof(datumCode)];
}

- (NSData *) getU16s
{
    NSData *dataBytes = [self getBytes];
    NSInteger totalLen = [dataBytes length];
    Byte bytes[totalLen];
    [dataBytes getBytes:bytes length:totalLen];
    NSInteger len = totalLen / 2;
    UInt16 dataU16s[len];
    Byte high, low;
    for (int i = 0; i < len; i++)
    {
        high = bytes[i * 2];
        low = bytes[i * 2 + 1];
        dataU16s[i] = [ESP_ByteUtil combine2bytesToU16WithHigh:high andLow:low] + EXTRA_LEN;
    }
    return [[NSData alloc]initWithBytes:dataU16s length:totalLen];
}

- (NSString *)description
{
    NSData* data = [self getBytes];
    return [ESP_ByteUtil getHexStringByData:data];
}


@end
