#include "lpw25api.h"
#include "ltrapi.h"
#include "ltedsapi.h"
#include "phasefilter.h"
#include <stdlib.h>
#include <string.h>
#include <math.h>

typedef struct  {
    t_phasefilter_state flt;
} t_lpw25_internal;

#define LPW25_R1            14000
#define LPW25_R2            (LPW25_R1/2)
#define LPW25_DEFAULT_C     (33e-6)
#define LPW25_INTERNAL_PARAMS(hnd) ((t_lpw25_internal *)(hnd->Internal))

#define LPW25_CAL_PHASE_FREQ 50

LTEDSAPI_DllExport(WORD) LTEDS_GetLCardManufacturerID(void);


/* Текстовые описания кодов ошибок. */
static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    {LPW25_ERROR_FD_NOT_SET,       "Не задана частота дискретизации сигнала"},
    {LPW25_ERROR_SENS_NOT_SET,     "Не задан коэффициент передачи модуля"},
    {LPW25_ERROR_PROC_NOT_STARTED, "Не запущена обработка данных"},
    {LPW25_ERROR_TEDS_MANUFACTURER_ID, "Неизвестный идентификатор производителя"},
    {LPW25_ERROR_TEDS_MODEL_ID,         "Незвестаная модель преобразователя"}
};


static void f_phase_coef_def_init(TLPW25_PHASE_SHIFT_COEFS *coef) {
    coef->PhaseShiftRefFreq = LPW25_CAL_PHASE_FREQ;
    coef->PhaseShift = phasefilter_calc_phi(LPW25_R1, LPW25_R2, LPW25_DEFAULT_C,
                                            coef->PhaseShiftRefFreq, 1e6);
}


LPW25API_DllExport(INT) LPW25_InfoInit(TLPW25_INFO *info) {
    memset(info, 0, sizeof(TLPW25_INFO));
    info->VersionNumber = 1;
    info->VersionLetter = 'A';

    info->CalInfo.CalPeriod = 365;
    info->CalInfo.ROutCoefs.R_I_2_86 = LPW25_ROUT_ISRC_2_86;
    info->CalInfo.ROutCoefs.R_I_10 = LPW25_ROUT_ISRC_10;

    f_phase_coef_def_init(&info->CalInfo.PhaseCoefs);
    return  LTR_OK;
}


LPW25API_DllExport(INT) LPW25_InfoStdFill(TLPW25_INFO *info, DWORD model_id) {
    INT err = LPW25_InfoInit(info);
    if (err == LTR_OK){
        info->ModelID = model_id;
        if ((model_id == LTEDS_LCARD_MODEL_ID_LPW25_U_2_230_CH1) ||
                (model_id == LTEDS_LCARD_MODEL_ID_LPW25_U_2_230_CH2)) {

            info->PhysicalMeasurand = LTEDS_PHYSMEAS_VOLTAGE;
            info->PhysicalRange = LPW25_PHYSRANGE_U_2_230;
            info->ElectricalRange = LPW25_PHYSRANGE_U_2_230 * LPW25_DEFAULT_SENS_U_2_230;
            info->CalInfo.CalCoef.Sens = LPW25_DEFAULT_SENS_U_2_230;
            info->CalInfo.CalCoef.InputValue = 230;
        } else if ((model_id == LTEDS_LCARD_MODEL_ID_LPW25_I_1_5_1) ||
                   (model_id == LTEDS_LCARD_MODEL_ID_LPW25_I_1_5_2)) {

            info->PhysicalMeasurand = LTEDS_PHYSMEAS_CURRENT;
            info->PhysicalRange = LPW25_PHYSRANGE_I_1_5;
            info->ElectricalRange = LPW25_PHYSRANGE_I_1_5 * LPW25_DEFAULT_SENS_I_1_5;
            info->CalInfo.CalCoef.Sens = LPW25_DEFAULT_SENS_I_1_5;
            info->CalInfo.CalCoef.InputValue = 5;
        }

        /* Для LPW25-I-1-5-2 не нормируем сдвиг фазы, т.к. там есть непредсказуемые
           ее изменения не корректируемые фильтром. Устанавливаем недействительные
           значения. */
        if (model_id == LTEDS_LCARD_MODEL_ID_LPW25_I_1_5_2) {
            info->CalInfo.PhaseCoefs.PhaseShiftRefFreq = 0;
            info->CalInfo.PhaseCoefs.PhaseShift = 0;
        } else {
            f_phase_coef_def_init(&info->CalInfo.PhaseCoefs);
        }
    }
    return err;
}

LPW25API_DllExport(INT) LPW25_TEDSDecode(const BYTE *teds_data, DWORD teds_size, DWORD flags,
                                         TLPW25_PROC_CTX *procctx, TLPW25_INFO *info,
                                         BOOLEAN *all_parsed) {
    TLTEDS_DECODE_CONTEXT dec;
    INT err;
    BOOLEAN full_info = FALSE;
    double sens_nom = 1;
    double sens_cbr_k = 1;
    double phys_max = 0;
    TLPW25_PHASE_SHIFT_COEFS phase_coef;

    phase_coef.PhaseShift = 0;
    phase_coef.PhaseShiftRefFreq = 0;

    if (info != NULL) {
        LPW25_InfoInit(info);
    }

    err = LTEDS_DecodeInit(&dec, teds_data, teds_size, 0);
    if (err == LTEDS_OK) {
        TLTEDS_INFO_BASIC basic;
        err = LTEDS_GetBasicInfo(&dec, &basic);
        if (err == LTEDS_OK) {
            if (!LTEDS_IsLCardManufacturerID(basic.ManufacturerID)) {
                err = LPW25_ERROR_TEDS_MANUFACTURER_ID;
            } else {
                if (info != NULL) {
                    err = LPW25_InfoStdFill(info, basic.ModelNumber);
                    info->VersionNumber = basic.VersionNumber;
                    info->VersionLetter = basic.VersionLetter;
                    info->SerialNumber = basic.SerialNumber;
                }
            }
        }

        if (err == LTEDS_OK) {
            BOOLEAN end = FALSE;
            while (!end && (err == LTEDS_OK)) {
                BYTE selector;
                err = LTEDS_GetSelector(&dec, &selector);
                if (err == LTEDS_OK) {
                    if (selector == LTEDS_SEL_STANDARD) {
                        BYTE template_id;
                        err = LTEDS_GetStdTemplateID(&dec, &template_id);
                        if (err == LTEDS_OK) {
                            if (template_id == LTEDS_STD_TEMPLATE_ID_HL_VOLTAGE_OUTPUT) {
                                TLTEDS_INFO_TMPLT_HLVOUT hlvout;
                                err = LTEDS_GetTmpltInfoHLVOut(&dec, &hlvout);
                                if (err == LTEDS_OK) {
                                    if (info != NULL) {
                                        info->PhysicalMeasurand = hlvout.Meas.Measurand;
                                        info->PhysicalRange = hlvout.Meas.MaxValue;
                                        info->ElectricalRange = hlvout.ElecSigOut.Range.MaxValue;

                                        info->CalInfo.CalDate = hlvout.CalInfo.Date;
                                        info->CalInfo.CalPeriod = hlvout.CalInfo.PeriodDays;
                                    }
                                    sens_nom = (hlvout.ElecSigOut.Range.MaxValue - hlvout.ElecSigOut.Range.MinValue)/
                                            (hlvout.Meas.MaxValue - hlvout.Meas.MinValue);
                                    phys_max = hlvout.Meas.MaxValue;
                                }
                            } else if (template_id == LTEDS_STD_TEMPLATE_ID_CAL_TABLE) {
                                TLTEDS_INFO_TMPLT_CAL_TABLE caltbl;
                                err = LTEDS_GetTmpltInfoCalTable(&dec, &caltbl);
                                if (err == LTEDS_OK) {
                                    if ((caltbl.DataSetCount > 0) && (caltbl.Domain == LTEDS_INFO_CAL_DOMAIN_PHYSICAL)) {
                                        sens_cbr_k = (caltbl.DataSets[0].DomainValue +
                                                caltbl.DataSets[0].RangeDeviation)/
                                                caltbl.DataSets[0].DomainValue;

                                        if (info) {
                                            info->CalInfo.CalCoef.InputValue = caltbl.DataSets[0].DomainValue * phys_max / 100;
                                            info->CalInfo.CalCoef.Sens = sens_nom * sens_cbr_k;
                                        }
                                    }
                                }
                            } else {
                                /** @todo skip template */
                                end = 1;
                            }
                        }
                    } else if (selector == LTEDS_SEL_MANUFACTURER) {
                        DWORD template_id;
                        err = LTEDS_GetManufacturerTemplateID(&dec, LTEDS_LCARD_TEMPLATE_ID_BITSIZE, &template_id);
                        if  (err == LTEDS_OK) {
                            if (template_id == LTEDS_LCARD_TEMPLATE_ID_CUR_IMP_TABLE) {
                                TLTEDS_LCARD_INFO_TMPLT_CUR_IMP curimp;
                                err = LTEDS_GetLCardTmpltInfoCurImpTable(&dec, &curimp);
                                if (err == LTEDS_OK)  {
                                    if (info != NULL) {
                                        BYTE pt_idx;
                                        for (pt_idx = 0; pt_idx < curimp.DataSetCount; pt_idx++) {
                                            if (fabs(curimp.DataSets[pt_idx].SourceCurrent - 10e-3) < 1e-6) {
                                                info->CalInfo.ROutCoefs.R_I_10 = curimp.DataSets[pt_idx].OutputImpedance;
                                            } else if (fabs(curimp.DataSets[pt_idx].SourceCurrent - 2.86e-3) < 1e-6) {
                                                info->CalInfo.ROutCoefs.R_I_2_86 = curimp.DataSets[pt_idx].OutputImpedance;
                                            }
                                        }
                                    }
                                }
                            } else if (template_id == LTEDS_LCARD_TEMPLATE_ID_PHASE_FREQ_TABLE) {
                                TLTEDS_LCARD_INFO_TMPLT_PHASE_FREQ pf;
                                err = LTEDS_GetLCardTmpltInfoPhaseFreqTable(&dec, &pf);
                                if ((err == LTEDS_OK) && (pf.DataSetCount > 0)) {                                    
                                    phase_coef.PhaseShift = pf.DataSets[0].PhaseShift;
                                    phase_coef.PhaseShiftRefFreq = pf.DataSets[0].Frequency;                                    
                                }
                            } else {
                                /** @todo skip template */
                            }
                       }
                    } else if (selector == LTEDS_SEL_OTHER_MANUFACTURER) {
                        end = 1;
                    } else if (selector == LTEDS_SEL_END) {
                        BYTE extsel;
                        err = LTEDS_GetEndExtendedSelector(&dec, &extsel);
                        if (err == LTEDS_OK) {
                            end = 1;
                            full_info = TRUE;
                        }
                    }
                }
            }
        }
    }

    if (err == LTEDS_OK) {

        if (procctx != NULL) {
            procctx->Sens = sens_nom * sens_cbr_k;
            procctx->PhaseCoefs = phase_coef;
        }

        if (info != NULL) {
            info->CalInfo.PhaseCoefs = phase_coef;
        }
    }

    if (all_parsed != NULL)
        *all_parsed = full_info;

    return err;
}

LPW25API_DllExport(INT) LPW25_TEDSEncode(BYTE *teds_data, DWORD teds_size, DWORD flags,
                                         const TLPW25_INFO *info, DWORD *result_bitsize) {
    TLTEDS_ENCODE_CONTEXT enc;
    INT err;

    err = LTEDS_EncodeInit(&enc, teds_data, teds_size, 0);
    if (err == LTR_OK) {
        TLTEDS_INFO_BASIC basic;
        basic.ManufacturerID = LTEDS_GetLCardManufacturerID();
        basic.ModelNumber = (WORD)info->ModelID;
        basic.SerialNumber = info->SerialNumber;
        basic.VersionLetter = info->VersionLetter;
        basic.VersionNumber = info->VersionNumber;
        err = LTEDS_PutBasicInfo(&enc, &basic);
    }

    if (err == LTEDS_OK) {
        err = LTEDS_PutSelector(&enc, LTEDS_SEL_STANDARD);
        if (err == LTEDS_OK)
            err = LTEDS_PutStdTemplateID(&enc, LTEDS_STD_TEMPLATE_ID_HL_VOLTAGE_OUTPUT);
        if (err == LTEDS_OK) {
            TLTEDS_INFO_TMPLT_HLVOUT hlv;
            hlv.Meas.Measurand = info->PhysicalMeasurand;
            hlv.Meas.MaxValue = info->PhysicalRange;
            hlv.Meas.MinValue = -info->PhysicalRange;
            hlv.ElecSigOut.Range.PrecType = LTEDS_INFO_HLV_ELEC_PREC_FULL;
            hlv.ElecSigOut.Range.MaxValue = info->ElectricalRange;
            hlv.ElecSigOut.Range.MinValue = -info->ElectricalRange;
            hlv.ElecSigOut.AcCoupling = TRUE;
            hlv.ElecSigOut.OutputImpedance = LPW25_ROUT_ISRC_10;
            hlv.ElecSigOut.ResponseTime = 1./LPW25_MAX_BANDWIDTH;
            hlv.PowerSupply.IsRequired = FALSE;
            hlv.CalInfo.Date = info->CalInfo.CalDate;
            hlv.CalInfo.PeriodDays = info->CalInfo.CalPeriod;
            hlv.CalInfo.Initials[0] = '\0';
            hlv.MeasLocationID = 0;
            err = LTEDS_PutTmpltInfoHLVOut(&enc, &hlv);
        }
    }

    if ((err == LTEDS_OK) && (info->CalInfo.CalCoef.InputValue > 0)) {
        err = LTEDS_PutSelector(&enc, LTEDS_SEL_STANDARD);
        if (err == LTEDS_OK)
            err = LTEDS_PutStdTemplateID(&enc, LTEDS_STD_TEMPLATE_ID_CAL_TABLE);
        if (err == LTEDS_OK) {
            TLTEDS_INFO_TMPLT_CAL_TABLE caltbl;
            double domain_val = 100. * info->CalInfo.CalCoef.InputValue
                    / info->PhysicalRange;
            double sens_nom = info->ElectricalRange/ info->PhysicalRange;

            caltbl.Domain = LTEDS_INFO_CAL_DOMAIN_PHYSICAL;
            caltbl.DataSetCount = 1;
            caltbl.DataSets[0].DomainValue = domain_val;
            caltbl.DataSets[0].RangeDeviation = domain_val * (info->CalInfo.CalCoef.Sens/sens_nom - 1);
            err = LTEDS_PutTmpltInfoCalTable(&enc, &caltbl);
        }
    }

    if (err == LTEDS_OK) {
        err = LTEDS_PutSelector(&enc, LTEDS_SEL_MANUFACTURER);
        if (err == LTEDS_OK) {
            err = LTEDS_PutManufacturerTemplateID(&enc, LTEDS_LCARD_TEMPLATE_ID_BITSIZE,
                                                  LTEDS_LCARD_TEMPLATE_ID_CUR_IMP_TABLE);
        }
        if (err == LTEDS_OK) {
            TLTEDS_LCARD_INFO_TMPLT_CUR_IMP curimp;
            curimp.PrecFormat = 0;
            curimp.DataSetCount = 2;
            curimp.DataSets[0].SourceCurrent = 2.86e-3;
            curimp.DataSets[0].OutputImpedance = info->CalInfo.ROutCoefs.R_I_2_86;
            curimp.DataSets[1].SourceCurrent = 10e-3;
            curimp.DataSets[1].OutputImpedance = info->CalInfo.ROutCoefs.R_I_10;
            err = LTEDS_PutLCardTmpltInfoCurImpTable(&enc, &curimp);
        }
    }

    if ((err == LTEDS_OK) && (info->CalInfo.PhaseCoefs.PhaseShiftRefFreq > 0)) {
        err = LTEDS_PutSelector(&enc, LTEDS_SEL_MANUFACTURER);
        if (err == LTEDS_OK) {
            err = LTEDS_PutManufacturerTemplateID(&enc, LTEDS_LCARD_TEMPLATE_ID_BITSIZE,
                                                  LTEDS_LCARD_TEMPLATE_ID_PHASE_FREQ_TABLE);
        }
        if (err == LTEDS_OK) {
            TLTEDS_LCARD_INFO_TMPLT_PHASE_FREQ phasefreq;
            phasefreq.DataSetCount = 1;
            phasefreq.DataSets[0].Frequency = info->CalInfo.PhaseCoefs.PhaseShiftRefFreq;
            phasefreq.DataSets[0].PhaseShift = info->CalInfo.PhaseCoefs.PhaseShift;
            err = LTEDS_PutLCardTmpltInfoPhaseFreqTable(&enc, &phasefreq);
        }
    }

    if (err == LTEDS_OK) {
        err = LTEDS_PutSelector(&enc, LTEDS_SEL_END);
        if (err == LTEDS_OK)
            err = LTEDS_PutEndExtendedSelector(&enc, LTEDS_EXTSEL_ASCII);
        if (err == LTEDS_OK)
            err = LTEDS_PutASCII(&enc, 7, "");
    }

    if (result_bitsize != NULL)
        *result_bitsize = enc.ProcBitPos;

    return  err;
}



LPW25API_DllExport(INT) LPW25_ProcessInit(TLPW25_PROC_CTX *ctx) {
    INT err = ctx == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        ctx->Internal = NULL;
        ctx->Sens = 0.;
        ctx->Fd = 0;
        f_phase_coef_def_init(&ctx->PhaseCoefs);
        memset(ctx->Reserved, 0, sizeof(ctx->Reserved));
    }
    return err;
}


LPW25API_DllExport(INT) LPW25_ProcessCopy(TLPW25_PROC_CTX *dst, const TLPW25_PROC_CTX *src) {
    INT err = (src == NULL) || (dst == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        dst->Sens = src->Sens;
        dst->Fd = src->Fd;
        dst->PhaseCoefs = src->PhaseCoefs;
        memcpy(&dst->Reserved, &src->Reserved, sizeof(src->Reserved));
        if (LPW25_ProcessIsRunning(src) == LTR_OK) {
            if (LPW25_ProcessIsRunning(dst) != LTR_OK) {
                err = LPW25_ProcessStart(dst);
            }

            if (err == LTR_OK) {
                memcpy(dst->Internal, src->Internal, sizeof(t_lpw25_internal));
            }
        } else if (LPW25_ProcessIsRunning(dst) == LTR_OK) {
            err = LPW25_ProcessStop(dst);
        }
    }
    return  err;
}





LPW25API_DllExport(INT) LPW25_ProcessStart(TLPW25_PROC_CTX *ctx) {
    INT err = ctx == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        if (ctx->Internal != NULL)
            LPW25_ProcessStop(ctx);

        if (ctx->Fd <= 0) {
            err = LPW25_ERROR_FD_NOT_SET;
        } else if (ctx->Sens <= 0) {
            err = LPW25_ERROR_SENS_NOT_SET;
        } else {
            t_lpw25_internal *internal = malloc(sizeof(t_lpw25_internal));
            if (internal == NULL) {
                err = LTR_ERROR_MEMORY_ALLOC;
            } else {
                ctx->Internal = internal;
                phasefilter_init(&internal->flt, LPW25_R1, LPW25_R2, ctx->PhaseCoefs.PhaseShift,
                                 ctx->PhaseCoefs.PhaseShiftRefFreq, ctx->Fd);
            }
        }
    }

    return err;
}

LPW25API_DllExport(INT) LPW25_ProcessData(TLPW25_PROC_CTX *ctx, const double *indata,
                                       double *outdata, DWORD size, DWORD flags) {
    INT err = ctx == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        if (ctx->Internal == NULL) {
            err = LPW25_ERROR_PROC_NOT_STARTED;
        }
    }
    if (err == LTR_OK) {
        DWORD i;
        t_lpw25_internal *internal = LPW25_INTERNAL_PARAMS(ctx);
        if (flags & LPW25_PROC_FLAG_NONCONT_DATA) {
            phasefilter_reset(&internal->flt);
        }

        for (i = 0; i < size; i++) {
            double val = *indata++;
            if (flags & LPW25_PROC_FLAG_PHYS)
                val = val / ctx->Sens;
            if (flags & LPW25_PROC_FLAG_PHASE_COR)
                val = phasefilter_process_point(&internal->flt, val);
            *outdata++ = val;
        }
    }
    return err;
}

LPW25API_DllExport(INT) LPW25_ProcessStop(TLPW25_PROC_CTX *ctx) {
    INT err = ctx == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        if (ctx->Internal) {
            free(ctx->Internal);
            ctx->Internal = NULL;
        }
    }
    return err;
}

LPW25API_DllExport(INT) LPW25_ProcessIsRunning(const TLPW25_PROC_CTX *ctx) {
    return  ctx == NULL ? LTR_ERROR_INVALID_MODULE_DESCR :
            ctx->Internal == NULL ? LPW25_ERROR_PROC_NOT_STARTED :
                                    LTR_OK;
}

LPW25API_DllExport(LPCSTR) LPW25_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 LTEDS_GetErrorString(err);
}
