#include "ltrt10api.h"
#include "ltrmodule.h"
#include <stdlib.h>
#include <string.h>
#include <math.h>

/* биты управляющего регистра DDS */
#define AD9834_CTLREG_BIT_B28       (1<<13UL)
#define AD9834_CTLREG_BIT_HLB       (1<<12UL)
#define AD9834_CTLREG_BIT_FSEL      (1<<11UL)
#define AD9834_CTLREG_BIT_PSEL      (1<<10UL)
#define AD9834_CTLREG_BIT_PIN_SW    (1<< 9UL)
#define AD9834_CTLREG_BIT_RESET     (1<< 8UL)
#define AD9834_CTLREG_BIT_SLEEP1    (1<< 7UL)
#define AD9834_CTLREG_BIT_SLEEP12   (1<< 6UL)
#define AD9834_CTLREG_BIT_OPBITEN   (1<< 5UL)
#define AD9834_CTLREG_BIT_SIGN_PIB  (1<< 4UL)
#define AD9834_CTLREG_BIT_DIV2      (1<< 3UL)
#define AD9834_CTLREG_BIT_MODE      (1<< 1UL)



#define AD9834_REGADDR_FREQ(num) ((num) ? 0x8000 : 0x4000)
#define AD9834_REGADDR_PHA(num)  ((num) ? 0xE000 : 0xC000)

/* команды модуля LTRT01 */
#define CMD_SW_CONTROL          (LTR010CMD_INSTR | 0x01)
#define CMD_SYNT_CONTROL        (LTR010CMD_INSTR | 0x02)
/* команда NOP должна передаваться после каждой команды CMD_SYNT_CONTROL */
#define CMD_NOP                 (LTR010CMD_INSTR | 0x03)

#define RESP_SW_CONTROL         (CMD_SW_CONTROL)
#define RESP_SYNT_CONTROL       (CMD_SYNT_CONTROL)

#define CMD_BITPOS_SW_CTL(ch)   (2*(ch)+8)
#define CMD_BITPOS_GAIN(stage)  (2*(stage))
#define CMD_BITPOS_DIV          (4)
#define CMD_BITPOS_DDS_RST      (7)

typedef struct {
    int code;
    double val;
    BYTE div;
    BYTE gain[LTRT10_DDS_GAIN_CNT];
} t_gain_str;


static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    { LTRT10_ERR_INVALID_SWITCH_POS, "Задан неверный код состояния коммутатора"},
    { LTRT10_ERR_INVALID_DDS_DIV,    "Задан неверный код коэффициента передачи выходного делителя сигнала DDS"},
    { LTRT10_ERR_INVALID_DDS_GAIN,   "Задан неверный код усиления для DDS"},
    { LTRT10_ERR_INVALID_FREQ_VAL,   "Неверно задан код частоты сигнала DDS"},
    { LTRT10_ERR_INVALID_DDS_AMP,    "Задан неверный код амплитуды сигнала DDS"},
    { LTRT10_ERR_GAIN2_EXCEED_GAIN1, "Коэф. усиления второй ступени превышает коэф. первой ступени"}
};

/* таблица вариантов выходных амплитуд сигнала и соответствие им параметров
 * усиления выходного тракта */
static const t_gain_str f_amp_tbl[] = {
    {LTRT10_DDS_OUT_AMP_9_6, 9.6, LTRT10_DDS_DIV_1,  {LTRT10_DDS_GAIN_4, LTRT10_DDS_GAIN_4}},
    {LTRT10_DDS_OUT_AMP_7_2, 7.2, LTRT10_DDS_DIV_0_75,  {LTRT10_DDS_GAIN_4, LTRT10_DDS_GAIN_4}},
    {LTRT10_DDS_OUT_AMP_4_8, 4.8, LTRT10_DDS_DIV_1,  {LTRT10_DDS_GAIN_4, LTRT10_DDS_GAIN_2}},
    {LTRT10_DDS_OUT_AMP_3_6, 3.6, LTRT10_DDS_DIV_0_75,  {LTRT10_DDS_GAIN_4, LTRT10_DDS_GAIN_2}},
    {LTRT10_DDS_OUT_AMP_2_4, 2.4, LTRT10_DDS_DIV_1,  {LTRT10_DDS_GAIN_2, LTRT10_DDS_GAIN_2}},
    {LTRT10_DDS_OUT_AMP_1_8, 1.8, LTRT10_DDS_DIV_0_75,  {LTRT10_DDS_GAIN_2, LTRT10_DDS_GAIN_2}},
    {LTRT10_DDS_OUT_AMP_1_2, 1.2, LTRT10_DDS_DIV_1,  {LTRT10_DDS_GAIN_2, LTRT10_DDS_GAIN_1}},
    {LTRT10_DDS_OUT_AMP_0_9, 0.9, LTRT10_DDS_DIV_0_75,  {LTRT10_DDS_GAIN_2, LTRT10_DDS_GAIN_1}},
    {LTRT10_DDS_OUT_AMP_0_6, 0.6, LTRT10_DDS_DIV_1,  {LTRT10_DDS_GAIN_1, LTRT10_DDS_GAIN_1}},
    {LTRT10_DDS_OUT_AMP_0_45, 0.45, LTRT10_DDS_DIV_0_75, {LTRT10_DDS_GAIN_1, LTRT10_DDS_GAIN_1}},
    {LTRT10_DDS_OUT_AMP_0_3, 0.3, LTRT10_DDS_DIV_0_5, {LTRT10_DDS_GAIN_1, LTRT10_DDS_GAIN_1}},
    {LTRT10_DDS_OUT_AMP_0_15, 0.15, LTRT10_DDS_DIV_0_25, {LTRT10_DDS_GAIN_1, LTRT10_DDS_GAIN_1}},
};

static const double f_gain_tbl[] = {4., 2., 1.};
static const double f_div_tbl[] = {1., 0.75, 0.5, 0.25};

static void f_info_init(TLTRT10 *hnd) {
    memset(&hnd->ModuleInfo, 0, sizeof(hnd->ModuleInfo));
    strcpy(hnd->ModuleInfo.Name, "LTRT10");

}

/* Формирование управляющей команды для установки коммутаторов и коэффициентов усиления */
static INT f_make_cmd_swctl(TLTRT10 *hnd, DWORD *cmd, BOOL rst) {
    INT err = 0;
    DWORD ch;
    WORD cmd_data=0;
    for (ch = 0; (ch < LTRT10_SWITCH_CNT) && (err == LTR_OK); ++ch) {
        if (hnd->Cfg.SwitchPos[ch]>LTRT10_SWITCH_POS_ZSTATE) {
            err = LTRT10_ERR_INVALID_SWITCH_POS;
        } else {
            cmd_data |= (WORD)hnd->Cfg.SwitchPos[ch] << CMD_BITPOS_SW_CTL(ch);
        }
    }

    for (ch = 0; (ch < LTRT10_DDS_GAIN_CNT) && (err == LTR_OK); ++ch) {
        if (hnd->Cfg.DDS.Gain[ch] >= sizeof(f_gain_tbl)/sizeof(f_gain_tbl[0])) {
            err = LTRT10_ERR_INVALID_DDS_GAIN;
        } else {
            cmd_data |= (WORD)hnd->Cfg.DDS.Gain[ch] << CMD_BITPOS_GAIN(ch);
        }
    }

    if ((err == LTR_OK) && (f_gain_tbl[hnd->Cfg.DDS.Gain[1]] > f_gain_tbl[hnd->Cfg.DDS.Gain[0]])) {
        err = LTRT10_ERR_GAIN2_EXCEED_GAIN1;
    }

    if (err == LTR_OK) {
        if (hnd->Cfg.DDS.AmpDiv >= sizeof(f_div_tbl)/sizeof(f_div_tbl[0])) {
            err = LTRT10_ERR_INVALID_DDS_DIV;
        } else {
            cmd_data |= (WORD)hnd->Cfg.DDS.AmpDiv << CMD_BITPOS_DIV;
        }
    }

    if ((err == LTR_OK) && rst) {
        cmd_data |= (1 << CMD_BITPOS_DDS_RST);
    }


    if ((err == LTR_OK) && (cmd != NULL)) {
        *cmd = LTR_MODULE_MAKE_CMD(CMD_SW_CONTROL, cmd_data);
    }

    return err;
}

/* формирование команды записи в управляющий регистр DDS.
    inv означает что нужно установить активным регистр частоты, противоположный
    используюмому сейчас */
static INT f_make_cmd_dds_ctl(TLTRT10 *hnd, DWORD *cmd, BOOL rst, BOOL invFreqReg) {
    DWORD data=AD9834_CTLREG_BIT_B28;
    if (hnd->CurDSSFreqReg!=invFreqReg)
        data|=AD9834_CTLREG_BIT_FSEL;
    if (rst)
        data|=AD9834_CTLREG_BIT_RESET;
    if (cmd)
        *cmd = LTR_MODULE_MAKE_CMD(CMD_SYNT_CONTROL, data);
    return LTR_OK;
}

/* Заполняем набор команд для смены частоты DDS. Набор состоит из 6-команд,
 * служащих для записи частоты в неиспользуемый сейчас регистр и переключения
 * DDS на новую частоту */
static INT f_make_cmds_dds_fsel(TLTRT10 *hnd, DWORD *cmd) {
    INT err = (hnd->Cfg.DDS.FreqVal==0) || (hnd->Cfg.DDS.FreqVal > LTRT10_DDS_FREQ_VAL_MAX) ?
                LTRT10_ERR_INVALID_FREQ_VAL : LTR_OK;
    if (err == LTR_OK) {
        BYTE freq_num = !hnd->CurDSSFreqReg;
        cmd[0] = LTR_MODULE_MAKE_CMD(CMD_SYNT_CONTROL, AD9834_REGADDR_FREQ(freq_num)
                                     | (hnd->Cfg.DDS.FreqVal & 0x3FFF));
        cmd[1] = LTR_MODULE_MAKE_CMD(CMD_NOP, 0);
        cmd[2] = LTR_MODULE_MAKE_CMD(CMD_SYNT_CONTROL, AD9834_REGADDR_FREQ(freq_num)
                                     | ((hnd->Cfg.DDS.FreqVal>>14) & 0x3FFF));
        cmd[3] = LTR_MODULE_MAKE_CMD(CMD_NOP, 0);
        err = f_make_cmd_dds_ctl(hnd, &cmd[4], hnd->DDSInReset, TRUE);
        if (err == LTR_OK) {
            cmd[5] = LTR_MODULE_MAKE_CMD(CMD_NOP, 0);
        }
    }
    return err;
}

/* установка или сброс бита RESET контрольного регистра DDS */
static INT f_set_dds_reset(TLTRT10 *hnd, BOOL reset) {
    DWORD cmd[2], resp[2];
    INT err = f_make_cmd_dds_ctl(hnd, &cmd[0], reset, 0);
    if (err == LTR_OK) {
        cmd[1] = LTR_MODULE_MAKE_CMD(CMD_NOP, 0);
        err = ltr_module_send_with_echo_resps(&hnd->Channel, cmd, 2, resp);
    }

    if (err == LTR_OK)
        hnd->DDSInReset = reset;

    return err;
}

static double f_get_cur_freq(TLTRT10_CONFIG *cfg) {
    return (double)cfg->DDS.FreqVal*LTRT10_DDS_CLK_FREQ/LTRT10_DDS_FREQ_DIV;
}

static double f_get_cur_dds_amp(TLTRT10_CONFIG *cfg) {
    unsigned ch;
    double val = 0.6 * f_div_tbl[cfg->DDS.AmpDiv];
    for (ch = 0; ch < LTRT10_DDS_GAIN_CNT; ++ch) {
        val *= f_gain_tbl[cfg->DDS.Gain[ch]];
    }
    return val;
}


LTRT10API_DllExport(INT) LTRT10_Init(TLTRT10 *hnd) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        unsigned ch;
        memset(hnd, 0, sizeof(TLTRT10));
        hnd->size = sizeof(TLTRT10);        
        f_info_init(hnd);
        err = LTR_Init(&hnd->Channel);

        if (err==LTR_OK)
            hnd->Cfg.DDS.FreqVal = LTRT10_DDS_FREQ_VAL_MAX/2;

        for (ch=0; ch < LTRT10_SWITCH_CNT; ++ch) {
            hnd->Cfg.SwitchPos[ch] = LTRT10_SWITCH_POS_ZERO;
        }
    }
    return err;
}


LTRT10API_DllExport(INT) LTRT10_Open(TLTRT10 *hnd, DWORD net_addr, WORD net_port,
                                     const CHAR *csn, WORD slot) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    BYTE pld_ver;
    if (err == LTR_OK) {
        err = LTR01_Open(&hnd->Channel, net_addr, net_port, csn, slot,
                         LTR01_SUBID_LTRT10, &pld_ver, NULL);
    }
    if (err == LTR_OK) {
        f_info_init(hnd);
        hnd->ModuleInfo.VerPLD = pld_ver;
    }

    if (err==LTR_OK) {
        err = f_set_dds_reset(hnd, TRUE);
    }

    if (err==LTR_OK) {
        DWORD cmds[4], resps[4];
        int pos=0;
        cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_SYNT_CONTROL, AD9834_REGADDR_PHA(0) | 1024);
        cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_NOP,0);
        cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_SYNT_CONTROL, AD9834_REGADDR_PHA(1) | 1024);
        cmds[pos++] = LTR_MODULE_MAKE_CMD(CMD_NOP,0);
        err = ltr_module_send_with_echo_resps(&hnd->Channel, cmds, pos, resps);
    }

    if (err == LTR_OK) {
        err = LTRT10_SetConfig(hnd);
    }

    if (err != LTR_OK)
        LTRT10_Close(hnd);

    return err;
}


LTRT10API_DllExport(INT) LTRT10_Close(TLTRT10 *hnd) {
    INT err = hnd==NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        err=LTR01_Close(&hnd->Channel);
    }
    return err;
}

LTRT10API_DllExport(INT) LTRT10_IsOpened(TLTRT10 *hnd) {
    return hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&hnd->Channel);
}


LTRT10API_DllExport(INT) LTRT10_SetConfig(TLTRT10 *hnd) {
    INT err = LTRT10_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD cmds[7], resp[7];
        INT pos = 0;
        err = f_make_cmd_swctl(hnd, &cmds[pos++], 0);
        if (err == LTR_OK) {
            err = f_make_cmds_dds_fsel(hnd, &cmds[pos]);
            if (err==LTR_OK)
                pos+=6;
        }

        if (err == LTR_OK) {
            err = ltr_module_send_with_echo_resps(&hnd->Channel, cmds, pos, resp);
        }

        if (err == LTR_OK) {
            /* при успешной установке частоты у нас сменился текущий регистр
             * для задания частоты */
            hnd->CurDSSFreqReg = !hnd->CurDSSFreqReg;
            hnd->DDSFreq = f_get_cur_freq(&hnd->Cfg);
            hnd->DDSAmp = f_get_cur_dds_amp(&hnd->Cfg);
        }
    }
    return err;
}

LTRT10API_DllExport(INT) LTRT10_FillDDSFreq(TLTRT10_CONFIG *cfg, double freq, double* set_freq) {
    INT err = cfg==NULL ? LTR_ERROR_PARAMETERS : LTR_OK;
    if (err == LTR_OK) {
        DWORD freq_val = (DWORD)(freq * LTRT10_DDS_FREQ_DIV/LTRT10_DDS_CLK_FREQ + 0.5);
        if (freq_val == 0) {
            freq_val = 1;
        }
        if (freq_val > LTRT10_DDS_FREQ_VAL_MAX) {
            freq_val = LTRT10_DDS_FREQ_VAL_MAX;
        }
        cfg->DDS.FreqVal = freq_val;

        if (set_freq != NULL) {
            *set_freq = f_get_cur_freq(cfg);
        }

    }
    return err;
}

LTRT10API_DllExport(INT) LTRT10_FillDDSOutAmpVolt(TLTRT10_CONFIG *cfg,
                                                   double value, double* set_value) {
    INT err = cfg == NULL ? LTR_ERROR_PARAMETERS : LTR_OK;
    if (err == LTR_OK) {
        unsigned fnd_pos = 0, i;
        double fnd_dv = fabs(f_amp_tbl[0].val-value);
        /* ищем в таблице наиболее близкое значение */
        for (i = 1; i < sizeof(f_amp_tbl)/sizeof(f_amp_tbl[0]); i++) {
            double dv = fabs(f_amp_tbl[i].val-value);
            if (dv < fnd_dv) {
                fnd_dv = dv;
                fnd_pos = i;
            }
        }

        for (i=0; i < LTRT10_DDS_GAIN_CNT; ++i) {
            cfg->DDS.Gain[i] = f_amp_tbl[fnd_pos].gain[i];
        }
        cfg->DDS.AmpDiv = f_amp_tbl[fnd_pos].div;
        if (set_value!=NULL)
            *set_value = f_amp_tbl[fnd_pos].val;
    }
    return err;
}

LTRT10API_DllExport(INT) LTRT10_FillDDSOutAmpCode(TLTRT10_CONFIG *cfg, DWORD code) {
    INT err = cfg==NULL ? LTR_ERROR_PARAMETERS : LTR_OK;
    if ((err == LTR_OK) && (code >= sizeof(f_amp_tbl)/sizeof(f_amp_tbl[0]))) {
        err = LTRT10_ERR_INVALID_DDS_AMP;
    }

    if (err == LTR_OK) {
        unsigned i;
        for (i = 0; i < LTRT10_DDS_GAIN_CNT; ++i) {
            cfg->DDS.Gain[i] = f_amp_tbl[code].gain[i];
        }
        cfg->DDS.AmpDiv = f_amp_tbl[code].div;
    }
    return err;
}



LTRT10API_DllExport(INT) LTRT10_SetDDSReset(TLTRT10 *hnd, BOOL reset) {
    INT err = LTRT10_IsOpened(hnd);
    if (err == LTR_OK) {
        err = f_set_dds_reset(hnd, reset);
    }
    return err;
}


LTRT10API_DllExport(LPCSTR) LTRT10_GetErrorString(INT err) {
    size_t i;
    for (i = 0; i < sizeof(f_err_tbl) / sizeof(f_err_tbl[0]); ++i) {
        if (f_err_tbl[i].code == err)
            return f_err_tbl[i].message;
    }
    return LTR_GetErrorString(err);
}



