// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2020 Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.


using System;
using System.IO;
using Hiperscan.Extensions.ApoIdentCommon.Scanner;
using Newtonsoft.Json.Linq;


namespace ScannerLib
{
    /// <summary>
    /// Description of PZNCode.
    /// </summary>
    /// 
    public class TScanResult
    {
        public Boolean Valid = false;
        public String Error = "";
        public String PZN = "";
        public String InternalNo = "";
        public String CodeType = "";
        public String OrderNo = "";
        public String FullCode = "";
        public String OrderName = "";
        public String Amount = "";


        public String NVE_00 = "";           // 18 stellig
        public String GTIN_01 = "";           // 14 stellig
        public String ChargeNo_10 = "";        // bis 20 stellig
        public String ManufctDate_11 = ""; //:TDateTime;
        public String PackDate_13 = ""; //:TDateTime;
        public String MHD_15 = ""; //TDateTime;
        public String ExpiryDate_17 = ""; //:TDateTime;
        public String ProductVer_20 = "";
        public String SerialNo_21 = "";            // bis 20 stellig
        public Int32 Count_30 = 1; //      :Integer;           // bis 8 stellig
        public Double NetWeight_kg_310 = 0.0; // :Double;
        public Double Area_qm_314 = 0.0;      // :Double;
        public Double Volume_l_315 = 0.0;     // :Double;
        public String OrderNoRecv_400 = "";
        public String DeliveryToGLN_410 = "";        // 13 stellig
        public String DeliveryToPLZ_421 = "";        // 3stellig + bis 9 stellig


        public String PrimaryDefaultName = "";
        public String PrimaryLatinName = "";
        public String SupplierName = "";
        public String SubstanceID = "";
    }

    public class PZNCode
    {
        //TScanResult lastscanres = new TScanResult();
        TScanResult justscanres;

        public PZNCode()
        {
            //lastscanres.Valid = false;
            //TScanResult justscanres = new TScanResult();
            justscanres = new TScanResult();
            justscanres.Valid = false;
        }

        void Log_e(String aname, String aValue)
        {
            Console.WriteLine("PZNCode: " + aname + "; " + aValue);
        }

        Int32 StrToIntDef(String avalue, Int32 deflt)
        {
            Int32 x = 0;
            if (!Int32.TryParse(avalue, out x))
                return deflt;
            else
                return x;
        }

        String StringsToDate8(String avalue)
        {
            switch (avalue.Length)
            {
                case 6:
                    return "20" + avalue;
                case 8:
                    return avalue;
                default:
                    return "19000101";
            }
        }

        public DateTime StringToDateTime(String aValue)
        {
            Int32 yyyy, mm, dd;
            aValue = StringsToDate8(aValue);
            if (!Int32.TryParse(aValue.Substring(0, 4), out yyyy))
                return new DateTime(1900, 1, 1);
            if (!Int32.TryParse(aValue.Substring(4, 2), out mm))
                return new DateTime(1900, 1, 2);
            if (!Int32.TryParse(aValue.Substring(6, 2), out dd))
                return new DateTime(1900, 1, 3);

            try
            {
                return new DateTime(yyyy, mm, dd);
            }
            catch
            {
                return ScannerInfo.ERROR_DATE;
            }

        }

        Boolean CheckGTIN(String avalue)
        {
            Int32 ii, chksum, chkval, jj;
            //Result := false;
            chkval = StrToIntDef(avalue.Substring(avalue.Length - 2), -1); // RightString(aValue, 1 ), -1 );
            if (chkval < 0)
                return false;

            chksum = 0;
            for (ii = 2; ii < avalue.Length - 2; ii++)
            {
                jj = StrToIntDef(avalue.Substring(ii, 1), -1);
                if (jj < 0)
                    return false;
                chksum = chksum + jj;
            }
            return true; // (chksum mod 10) = chkval;
        }

        Boolean CheckPZN8(String aValue)
        {
            Int32 ii, jj, sum = 0, x = 0;
            String ss;

            if (!Int32.TryParse(aValue, out x))
                return false;
            if (aValue.Length < 8)
                return false;

            for (ii = 0; ii < 7; ii++)
            {
                ss = aValue.Substring(ii, 1);
                Int32.TryParse(ss, out jj);
                sum = sum + jj * (ii + 1);
            }
            sum = sum % 11;
            if (sum > 9)
                return false;
            ss = aValue.Substring(7, 1);
            return ss.Equals(sum.ToString());
        }

        Boolean CheckPZN7(String aValue)
        {
            Int32 ii, jj, sum = 0, x = 0;
            String ss;

            if (!Int32.TryParse(aValue, out x))
                return false;
            if (aValue.Length < 7)
                return false;

            for (ii = 0; ii < 6; ii++)
            {
                ss = aValue.Substring(ii, 1);
                Int32.TryParse(ss, out jj);
                sum = sum + jj * (ii + 2);
            }
            sum = sum % 11;
            if (sum > 9)
                return false;
            ss = aValue.Substring(6, 1);
            return ss.Equals(sum.ToString());
        }

        public TScanResult DecodePZN(String aValue)
        {
            TScanResult res = new TScanResult();
            String ss;
            res.Valid = false;
            res.PZN = "";

            res.Error = "OK";
            res.CodeType = "CODE_39";

            if (!aValue.StartsWith("-"))
            {
                res.Error = "NOK_PZN-";
                ss = aValue;
            }
            else
                ss = aValue.Substring(1);

            switch (ss.Length)
            {
                case 7:
                    res.PZN = ss;
                    res.Valid = CheckPZN7(ss);
                    if (!res.Valid)
                    {
                        res.PZN = ss + "F";
                        res.Error = "NOK_PZN7";
                    }
                    break;
                case 8:
                    res.PZN = ss;
                    res.Valid = CheckPZN8(ss);
                    if (!res.Valid)
                    {
                        res.PZN = ss + "F";
                        res.Error = "NOK_PZN8";
                    }
                    break;

                default:
                    res.Error = "NOK_PZN";
                    break;
            }
            return res;
        }

        public TScanResult DecodeGS1(String aValue)
        {

            TScanResult res = new TScanResult();
            String smark, ss, stin, spref, spzn, sentry, sgs = "\u001d";
            Int32 posi, posj, posc, ipref, nkom;

            res.Valid = false;
            res.CodeType = "GS1";
            res.Error = "Unknown";
            res.FullCode = aValue;
            posj = 0;

            try
            {
                while (posj < res.FullCode.Length)
                {
                    posc = aValue.IndexOf(sgs);
                    if (posc > 0)
                    {
                        sentry = aValue.Substring(0, posc);
                        aValue = aValue.Replace(sentry + sgs, "");
                    }
                    else
                        sentry = aValue;
                    Log_e(posj.ToString(), aValue);

                    posj = posj + sentry.Length + 1;

                    posi = 0;
                    while (posi < sentry.Length)
                    {
                        smark = sentry.Substring(posi, 2);
                        posi = posi + 2;

                        Log_e(posi.ToString(), smark + " - " + sentry);

                        if (smark.Equals("01"))
                        {
                            stin = sentry.Substring(posi);
                            spref = stin.Substring(0, 5);
                            ipref = StrToIntDef(stin.Substring(0, 3), 0);
                            switch (ipref)
                            {
                                case 41:   // NTIN ?
                                    if (spref.Equals("04150"))
                                    {       // NTIN Prefix
                                        spzn = stin.Substring(4, 9);
                                        posi = posi + 14;                 // ignore GTIN checksum
                                        if (spzn.StartsWith("0"))
                                        {
                                            spzn = spzn.Substring(1); // RightString( spzn, spzn.length()-1);
                                            if (CheckPZN7(spzn))
                                                res.PZN = spzn;
                                            else
                                            {
                                                if (CheckPZN8(spzn))
                                                    res.PZN = spzn;
                                            }
                                            res.Valid = !res.PZN.Equals("");
                                            if (!res.Valid)
                                                res.Error = "NOK_PZN";
                                        }
                                    }
                                    break;

                                case 40:
                                case 42:
                                case 43:   // GTIN
                                    res.GTIN_01 = stin.Substring(4, 10); // stin, stin.length()-1);
                                    posi = posi + 14;
                                    res.Valid = CheckGTIN(res.GTIN_01);
                                    if (!res.Valid)
                                        res.Error = "NOK_GTIN";
                                    break;
                                default:
                                    res.Error = "NOK_GTIN";
                                    break;
                            }
                            continue;
                        }

                        if (smark.Equals("10"))
                        {
                            ss = sentry.Substring(posi);
                            posi = posi + ss.Length;
                            res.ChargeNo_10 = ss;
                            continue;
                        }

                        if (smark.Equals("11"))
                        {
                            ss = sentry.Substring(posi, 6);
                            posi = posi + 6; //Inc( posi, 6 );
                            res.ManufctDate_11 = StringsToDate8(ss.Substring(0, 6));
                            continue;
                        }

                        if (smark.Equals("13"))
                        {
                            ss = sentry.Substring(posi, 6);
                            posi = posi + 6;
                            res.PackDate_13 = StringsToDate8(ss.Substring(0, 6));
                            continue;
                        }

                        if (smark.Equals("15"))
                        {
                            ss = sentry.Substring(posi, 6);
                            posi = posi + 6;
                            res.MHD_15 = StringsToDate8(ss.Substring(0, 6));
                            continue;
                        }

                        if (smark.Equals("17"))
                        {
                            ss = sentry.Substring(posi, 8);
                            if (sentry.Length == 36)
                            {  // bugfix for false 8 byte date
                                posi = posi + 8;
                                res.ExpiryDate_17 = StringsToDate8(ss.Substring(0, 8));
                            }
                            else
                            {
                                posi = posi + 6;
                                res.ExpiryDate_17 = StringsToDate8(ss.Substring(0, 6));
                            }
                            continue;
                        }

                        if (smark.Equals("20"))
                        { //Produktvariante
                            ss = sentry.Substring(posi, 2);
                            posi = posi + 2;
                            res.ProductVer_20 = ss;
                            continue;
                        }

                        if (smark.Equals("21"))
                        {
                            ss = sentry.Substring(posi);
                            posi = posi + ss.Length;
                            res.SerialNo_21 = ss;
                            continue;
                        }

                        if (smark.Equals("30"))
                        {
                            ss = sentry.Substring(posi, 9);
                            posi = posi + ss.Length;
                            res.Count_30 = StrToIntDef(ss, 0);
                            continue;
                        }

                        if (smark.Equals("31"))
                        {
                            ss = sentry.Substring(posi, 8);//88 );
                            ipref = StrToIntDef(sentry.Substring(posi, 1), 88);
                            posi = posi + 8;
                            nkom = StrToIntDef(sentry.Substring(posi, 1), 0);
                            posi = posi + 1;
                            ss = sentry.Substring(posi, 6);
                            posi = posi + 6;
                            switch (ipref)
                            {
                                case 0:
                                    res.NetWeight_kg_310 = StrToIntDef(ss, 0) / Math.Pow(10, nkom);
                                    break;
                                case 4:
                                    res.Area_qm_314 = StrToIntDef(ss, 0) / Math.Pow(10, nkom);
                                    break;
                                case 5:
                                    res.Volume_l_315 = StrToIntDef(ss, 0) / Math.Pow(10, nkom);
                                    break;
                            }
                            continue;
                        }

                        if (smark.Equals("40") || smark.Equals("41") || smark.Equals("42"))
                        {
                            smark = smark + sentry.Substring(posi, 1);
                            posi = posi + 1;
                            ipref = StrToIntDef(smark, 88);
                            switch (ipref)
                            {
                                case 400:
                                    ss = sentry.Substring(posi, 31);
                                    posi = posi + ss.Length;
                                    res.DeliveryToGLN_410 = ss;
                                    break;
                                case 410:
                                    res.DeliveryToGLN_410 = sentry.Substring(posi, 13);
                                    posi = posi + 13;
                                    break;
                                case 421:
                                    res.DeliveryToPLZ_421 = sentry.Substring(posi, 3);
                                    posi = posi + 3;
                                    ss = sentry.Substring(posi, 10);
                                    posi = posi + ss.Length;
                                    res.DeliveryToPLZ_421 = res.DeliveryToPLZ_421 + ss;
                                    break;
                            }
                            continue;
                        }
                    } // while posi
                } // while posj
            }
            catch (Exception e)
            {
                Log_e("DecodeGS1", e.Message);
                res.Valid = false;
            }
            res.Error = "OK";
            return res;
        }

        public TScanResult DecodeHCK(String aValue)
        {
            TScanResult res = new TScanResult();
            String ss, s1, sentry, smark;
            Int32 posi, posc;

            res.Valid = false;
            res.CodeType = "HCK";
            res.Error = "Unknown";
            res.FullCode = aValue;

            try
            {
                posi = 0;
                while (posi < res.FullCode.Length)
                {
                    posc = aValue.IndexOf("#");
                    if (posc > 0)
                    {
                        sentry = aValue.Substring(0, posc);
                        aValue = aValue.Replace(sentry + "#", "");
                    }
                    else
                        sentry = aValue;

                    posi = posi + sentry.Length + 1;
                    smark = sentry.Substring(0, 2);

                    if (smark.Equals("17"))
                    {
                        res.ExpiryDate_17 = StringsToDate8(sentry.Substring(2, 6));
                        res.Valid = true;
                        continue;
                    }

                    if (smark.Equals("02"))
                    {
                        res.InternalNo = sentry.Substring(2, 14);
                        res.SerialNo_21 = res.InternalNo;
                        s1 = sentry.Substring(16); //, 26);
                        if (s1.StartsWith("10"))
                        {
                            res.ChargeNo_10 = s1.Substring(2);
                            res.Valid = true;
                        }
                        continue;
                    }

                    if (sentry.StartsWith("240"))
                    {
                        res.OrderNo = sentry.Substring(3, 7);
                        if (res.OrderNo.StartsWith("H"))
                            res.SupplierName = "Hepart AG";
                        continue;
                    }

                    if (smark.Equals("90"))
                    {
                        res.OrderName = sentry.Substring(2);
                        ss = res.OrderName;
                        if (ss.Contains(","))
                        {
                            posc = ss.IndexOf(",");
                            if (posc > 0)
                            {
                                res.PrimaryDefaultName = ss.Substring(0, posc);
                                ss = ss.Substring(posc + 1);
                            }
                        }
                        if (ss.EndsWith("g"))
                            res.Amount = ss.Trim();
                        continue;
                    }

                    if (smark.Equals("01"))
                    {
                        ss = sentry.Substring(2);
                        String spref = ss.Substring(0, 5);
                        Int32 ipref = StrToIntDef(ss.Substring(0, 3), 0);
                        switch (ipref)
                        {
                            case 41:   // NTIN ? PZN
                                posi = posi + 14;
                                if (spref.Equals("04150"))
                                {       // NTIN Prefix
                                    String spzn = ss.Substring(5, 8);
                                    if (CheckPZN8(spzn))
                                        res.PZN = spzn;
                                    res.Valid = !res.PZN.Equals("");
                                    if (!res.Valid)
                                        res.Error = "NOK_PZN";
                                }
                                break;

                            case 40:
                            case 42:
                            case 43:   // GTIN
                                posi = posi + 14;
                                res.GTIN_01 = ss.Substring(4, 10);
                                res.Valid = CheckGTIN(res.GTIN_01);
                                if (!res.Valid)
                                    res.Error = "NOK_GTIN";
                                break;
                            default:
                                res.Error = "NOK_GTIN";
                                break;
                        }
                        continue;
                    }
                }
            }
            catch (Exception e)
            {
                Log_e("DecodeHCK", e.Message);
                res.Valid = false;
            }
            return res;
        }

        public TScanResult readScannerData(String aValue, String codeFormat)
        {
            String ss;
            TScanResult res = new TScanResult();
            res.Valid = false;
            res.CodeType = codeFormat;
            res.Error = "Unknown";


            if (!aValue.StartsWith("#"))
            {
                Log_e("readScannerData", "Code prefix fail: " + aValue);
                res.Error = "PrefixFail";
                res.FullCode = aValue;
                return res;
            }

            switch (codeFormat)
            {
                case "DATA_MATRIX":
                case "HCK":
                case "GS1":
                    // if ((aValue.length() > 30) && (aValue.length() < 201)) {
                    if (aValue.StartsWith("#\u001d"))
                        aValue = aValue.Replace("#\u001d", "#");

                    if (aValue.StartsWith("#01"))
                    {
                        res = DecodeGS1(aValue.Substring(1));
                    }

                    //#02076401227844761018000789#17200106930#240H141111#910#920#90HCK CamuCamu 1x30, 100g
                    if (aValue.StartsWith("#02"))
                    {
                        res = DecodeHCK(aValue.Substring(1));
                    }
                    break;
                //} break;

                case "CODE_39":
                    switch (aValue.Length)
                    {
                        case 8:
                        case 9:
                        case 10:    // PZN7 mit '-' oder PZN 8
                            res = DecodePZN(aValue);
                            break;
                    }
                    break;
                case "CODE_128":
                    // length 15
                    res.Error = "OK";
                    //justscanres.CodeType = "CODE_128";
                    ss = aValue.Substring(0, 6);
                    res.InternalNo = ss;
                    ss = aValue.Substring(6, 9);
                    res.ChargeNo_10 = ss;
                    res.Valid = true;
                    break;

                case "UPC_E":
                    // case 14: // Hepart Beispiel EAN13,  #7640122785336
                    res.Error = "OK";
                    res.InternalNo = aValue.Substring(1);
                    res.Valid = true;
                    break;
                case "EAN_13":
                    // Buch //case 14: // Hepart Beispiel EAN13,  #7640122785336
                    res.Error = "OK";
                    res.InternalNo = aValue.Substring(1);
                    res.Valid = true;
                    break;
                default:
                    res.Error = "CodeFail";
                    return res;
            }

            // sPZN = res.PZN;
            if (aValue.StartsWith("#"))
                res.FullCode = aValue.Substring(1);
            else
                res.FullCode = aValue;
            return res;
        }  // readScannerData



        public TScanResult readScannerFile(String fname)
        {
            TScanResult res = new TScanResult();

            try
            {
                JObject o1 = JObject.Parse(File.ReadAllText(fname));
                res.CodeType = o1["format"].ToObject<string>();
                res.FullCode = o1["content"].ToObject<string>();
                res.PZN = o1["pzn"].ToObject<string>();
                res.ExpiryDate_17 = o1["expiry"].ToObject<string>();
                res.ChargeNo_10 = o1["chargeno"].ToObject<string>();
                res.Valid = !res.FullCode.Equals("");
            }
            catch (Exception e)
            {
                Log_e("readScannerFile", e.Message);
                res.Valid = false;
            }
            return res;
        }


        public Boolean writeScannerFile(String fname, TScanResult avalue)
        {
            try
            {
                using (StreamWriter file =
                    new StreamWriter(fname))
                {
                    file.WriteLine("{");
                    file.WriteLine("\"format\": " + "\"" + avalue.CodeType + "\",");
                    file.WriteLine("\"content\": " + "\"" + avalue.FullCode + "\",");
                    file.WriteLine("\"pzn\": " + "\"" + avalue.PZN + "\",");
                    file.WriteLine("\"expiry\": " + "\"" + avalue.ExpiryDate_17 + "\",");
                    file.WriteLine("\"chargeno\": " + "\"" + avalue.ChargeNo_10 + "\"");
                    file.WriteLine("}");
                }
            }
            catch (Exception e)
            {
                Log_e("writeScannerFile", e.Message);
                return false;
            }
            return true;
        }



        public TScanResult InterpretScannerData(String aValue, String codeFormat)
        {
            String ss;
            //          lastscanres.PZN      = justscanres.PZN;
            //          lastscanres.CodeType = justscanres.CodeType;
            //          lastscanres.FullCode = justscanres.FullCode;


            justscanres.Valid = false;
            justscanres.PZN = "";
            justscanres.ExpiryDate_17 = "";
            justscanres.ChargeNo_10 = "";
            justscanres.InternalNo = "";
            justscanres.CodeType = codeFormat;
            justscanres.SerialNo_21 = "";
            justscanres.OrderNo = "";
            justscanres.Error = "Unknown";


            if (!aValue.StartsWith("#"))
            {
                Log_e("InterpretScannerData", "Code prefix fail: " + aValue.Substring(0, 1));
                justscanres.Error = "PrefixFail";
                justscanres.FullCode = aValue;
                return justscanres;
            }

            switch (codeFormat)
            {
                case "DATA_MATRIX":
                    if (aValue.StartsWith("#\u001d"))
                        aValue = aValue.Replace("#\u001d", "#");

                    if (aValue.StartsWith("#01"))
                    {
                        Log_e("InterpretScannerData", "DecodeGS1 " + aValue.Substring(1));
                        justscanres = DecodeGS1(aValue.Substring(1));
                        //                      if (justscanres.PZN.Equals("")) {
                        //                          if (lastscanres.CodeType.Equals("Code39")) {
                        //                              justscanres.PZN = lastscanres.PZN;
                        //                              lastscanres.PZN = "";
                        //                              //justscanres.Error = "AddedPZN";
                        //                          }
                        //                      }
                    }

                    //#02076401227844761018000789#17200106930#240H141111#910#920#90HCK CamuCamu 1x30, 100g
                    if (aValue.StartsWith("#02"))
                    {
                        Log_e("InterpretScannerData", "DecodeHCK " + aValue.Substring(1));
                        justscanres = DecodeHCK(aValue.Substring(1));
                    }
                    break;
                //} break;

                case "CODE_39":
                    switch (aValue.Length)
                    {
                        case 8:
                        case 9:
                        case 10:    // PZN7 mit '-' oder PZN 8
                            justscanres = DecodePZN(aValue.Substring(1));
                            break;
                    }
                    break;

                case "CODE_128":
                    // length 15
                    justscanres.Error = "OK";
                    justscanres.CodeType = "Code128";
                    ss = aValue.Substring(0, 6);
                    justscanres.InternalNo = ss;
                    ss = aValue.Substring(6, 9);
                    justscanres.ChargeNo_10 = ss;
                    justscanres.Valid = true;
                    break;

                case "UPC_E":
                    // case 14: // Hepart Beispiel EAN13,  #7640122785336
                    justscanres.Error = "OK";
                    justscanres.InternalNo = aValue.Substring(1);
                    justscanres.Valid = true;
                    break;
                case "EAN_13":
                    // Buch //case 14: // Hepart Beispiel EAN13,  #7640122785336
                    justscanres.Error = "OK";
                    justscanres.InternalNo = aValue.Substring(1);
                    justscanres.Valid = true;
                    break;
                default:
                    justscanres.Error = "OK";
                    justscanres.Valid = true;
                    break;
            }

            // sPZN = res.PZN;
            if (aValue.StartsWith("#"))
                justscanres.FullCode = aValue.Substring(1);
            else
                justscanres.FullCode = aValue;

            /*if (!sPZN.equals("")) {
                if (!sPZN.contains("F")) {
    //                        if Assigned(GPZNFile) {
    //                            ss=GPZNFile.ReadString(sPZN, "PrimaryDefaultName", "_");
    //                            res.PrimaryDefaultName =
    //                            ss; //GPZNFile.ReadString( sPZN, "PrimaryDefaultName", "_" );
    //                            res.PrimaryLatinName   =
    //                            GPZNFile.ReadString(sPZN, "PrimaryLatinName", "_");
    //                            res.SupplierName       =GPZNFile.ReadString(sPZN, "SupplierName", "_");
    //                            res.SubstanceID        =GPZNFile.ReadString(sPZN, "SubstanceID", "_");
    //                            res.Amount             =GPZNFile.ReadString(sPZN, "Amount", "_");
    //                        }
                    res.Valid = true;
                }
            }*/
            return justscanres;
        }  // InterpretScannerData

    }
}
