private static double CalculatePerLevel(string parameter, int?op, int?op_param, string stat)
        {
            var para = Utility.ToNullableInt(parameter);

            if (!para.HasValue)
            {
                throw ItemStatCostException.Create($"Could not calculate per level, as parameter '{parameter}' is not a valid integer");
            }

            var val = para.Value / 8d;

            return(val);
        }
        public string PropertyString(int?value, int?value2, string parameter, int itemLevel)
        {
            string lstValue;
            var    valueString = GetValueString(value, value2);

            if (DescriptonStringPositive == null)
            {
                lstValue = Stat;
            }
            else if (value2.HasValue && value2.Value < 0)
            {
                lstValue = DescriptionStringNegative;
            }
            else
            {
                lstValue = DescriptonStringPositive;
            }

            if (DescriptionFunction.HasValue)
            {
                if (DescriptionFunction.Value >= 1 && DescriptionFunction.Value <= 4 && lstValue.Contains("%d"))
                {
                    valueString      = lstValue.Replace("%d", valueString);
                    DescriptionValue = 3;
                }
                else
                {
                    switch (DescriptionFunction.Value)
                    {
                    case 1:
                        valueString = $"+{valueString}";
                        break;

                    case 2:
                        valueString = $"{valueString}%";
                        break;

                    case 3:
                        valueString = $"{valueString}";
                        break;

                    case 4:
                        valueString = $"+{valueString}%";
                        break;

                    case 5:
                        if (value.HasValue)
                        {
                            value = value * 100 / 128;
                        }
                        if (value2.HasValue)
                        {
                            value2 = value2 * 100 / 128;
                        }
                        valueString = GetValueString(value, value2);
                        valueString = $"{valueString}%";
                        break;

                    case 6:
                        double val1 = 0;
                        double val2 = 0;

                        if (value.HasValue && value2.HasValue)
                        {
                            val1 = CalculatePerLevel(value.Value.ToString(), Op, OpParam, Stat);
                            val2 = CalculatePerLevel(value2.Value.ToString(), Op, OpParam, Stat);
                        }
                        else
                        {
                            val1 = CalculatePerLevel(parameter, Op, OpParam, Stat);
                            val2 = CalculatePerLevel(parameter, Op, OpParam, Stat);
                        }

                        valueString      = GetValueString(val1, val2);
                        lstValue         = DescriptonStringPositive;
                        DescriptionValue = 3;
                        valueString      = $"+({valueString} Per Character Level) {Math.Floor(val1).ToString(CultureInfo.InvariantCulture)}-{Math.Floor(val2 * 99).ToString(CultureInfo.InvariantCulture)} {lstValue} (Based on Character Level)";
                        break;

                    case 7:
                        val1 = 0;
                        val2 = 0;

                        if (value.HasValue && value2.HasValue)
                        {
                            val1 = CalculatePerLevel(value.Value.ToString(), Op, OpParam, Stat);
                            val2 = CalculatePerLevel(value2.Value.ToString(), Op, OpParam, Stat);
                        }
                        else
                        {
                            val1 = CalculatePerLevel(parameter, Op, OpParam, Stat);
                            val2 = CalculatePerLevel(parameter, Op, OpParam, Stat);
                        }

                        valueString      = GetValueString(val1, val2);
                        lstValue         = DescriptonStringPositive;
                        DescriptionValue = 3;
                        valueString      = $"({valueString}% Per Character Level) {Math.Floor(val1).ToString(CultureInfo.InvariantCulture)}-{Math.Floor(val2 * 99).ToString(CultureInfo.InvariantCulture)}% {lstValue} (Based on Character Level)";
                        break;

                    case 8:
                        val1 = 0;
                        val2 = 0;

                        if (value.HasValue && value2.HasValue)
                        {
                            val1 = CalculatePerLevel(value.Value.ToString(), Op, OpParam, Stat);
                            val2 = CalculatePerLevel(value2.Value.ToString(), Op, OpParam, Stat);
                        }
                        else
                        {
                            val1 = CalculatePerLevel(parameter, Op, OpParam, Stat);
                            val2 = CalculatePerLevel(parameter, Op, OpParam, Stat);
                        }

                        valueString      = GetValueString(val1, val2);
                        lstValue         = DescriptonStringPositive;
                        DescriptionValue = 3;
                        valueString      = $"+({valueString} Per Character Level) {Math.Floor(val1).ToString(CultureInfo.InvariantCulture)}-{Math.Floor(val2 * 99).ToString(CultureInfo.InvariantCulture)} {lstValue} (Based on Character Level)";
                        break;

                    case 9:
                        val1 = 0;
                        val2 = 0;

                        if (value.HasValue && value2.HasValue)
                        {
                            val1 = CalculatePerLevel(value.Value.ToString(), Op, OpParam, Stat);
                            val2 = CalculatePerLevel(value2.Value.ToString(), Op, OpParam, Stat);
                        }
                        else
                        {
                            val1 = CalculatePerLevel(parameter, Op, OpParam, Stat);
                            val2 = CalculatePerLevel(parameter, Op, OpParam, Stat);
                        }

                        valueString      = GetValueString(val1, val2);
                        lstValue         = DescriptonStringPositive;
                        DescriptionValue = 3;
                        valueString      = $"{lstValue} {Math.Floor(val1).ToString(CultureInfo.InvariantCulture)}-{Math.Floor(val2 * 99).ToString(CultureInfo.InvariantCulture)} ({valueString} Per Character Level)";
                        break;

                    case 11:
                        valueString      = lstValue.Replace("%d", $"{((double)(Utility.ToNullableInt(parameter).Value / 100f)).ToString(CultureInfo.InvariantCulture)}");
                        DescriptionValue = 3;
                        break;

                    case 12:
                        valueString = $"+{valueString}";
                        break;

                    case 13:
                        var classReplace = "";
                        valueString = $"+{valueString}";

                        var regex = Regex.Match(parameter, @"randclassskill(\d+)");     // Work with custom randclasskill(digit)
                        if (regex.Success)
                        {
                            classReplace = "(Random Class)";
                            valueString  = $"+{regex.Groups[1].Value}";
                        }
                        else if (parameter == "randclassskill")
                        {
                            classReplace = "(Random Class)";
                            valueString  = "+3";
                        }
                        else
                        {
                            if (!CharStat.CharStats.ContainsKey(parameter))
                            {
                                throw ItemStatCostException.Create($"Could not find character class '{parameter}'\nNote: if you have made a custom version of 'randclassskill' to support different amount of skills change them to 'randclassskill<d>' for example 'randclassskill5' is supported.");
                            }
                            classReplace = CharStat.CharStats[parameter].Class;
                        }
                        lstValue = lstValue.Replace("%d", classReplace);
                        break;

                    case 14:
                        var par = Utility.ToNullableInt(parameter);
                        if (!par.HasValue)
                        {
                            throw ItemStatCostException.Create($"Could not convert parameter '{parameter}' to a valid integer");
                        }

                        if (!CharStat.SkillTabs.ContainsKey(par.Value))
                        {
                            throw ItemStatCostException.Create($"Could not find skill tab with id {par.Value}");
                        }

                        var skillTab = CharStat.SkillTabs[par.Value];

                        var className = CharStat.CharStats.Values.First(x => x.StrSkillTab1 == skillTab || x.StrSkillTab2 == skillTab || x.StrSkillTab3 == skillTab).Class;

                        if (!Table.Tables.ContainsKey(skillTab))
                        {
                            throw ItemStatCostException.Create($"Could not find translation key '{skillTab}' in any .tbl file");
                        }

                        lstValue    = Table.Tables[skillTab];
                        valueString = $"{lstValue.Replace("%d", valueString)} ({className} only)";
                        break;

                    case 15:
                        valueString = value2.Value.ToString();
                        var skill = Skill.GetSkill(parameter);

                        if (value2.Value == 0)
                        {
                            val1 = Math.Min(Math.Ceiling((itemLevel - (skill.RequiredLevel - 1)) / 3.9), 20);
                            val2 = Math.Min(Math.Round((99 - (skill.RequiredLevel - 1)) / 3.9), 20);

                            valueString = GetValueString(val1, val2);
                        }

                        valueString = lstValue.Replace("%d%", value.Value.ToString())
                                      .Replace("%d", valueString)
                                      .Replace("%s", skill.SkillDesc);

                        if (string.IsNullOrEmpty(skill.SkillDesc))
                        {
                            throw ItemStatCostException.Create($"Skill for property has missing 'skilldesc' in Skills.txt: name: '{skill.Name}', id: '{skill.Id}'");
                        }

                        break;

                    case 16:
                        valueString = lstValue.Replace("%d", valueString)
                                      .Replace("%s", Skill.GetSkill(parameter).Name);
                        DescriptionValue = 3;
                        break;

                    case 20:
                        valueString = $"{GetValueString(value * -1, value2 * -1)}%";
                        break;

                    case 23:
                        if (!MonStat.MonStats.ContainsKey(parameter))
                        {
                            throw ItemStatCostException.Create($"Could not find monster with id '{parameter}' in MonStats.txt");
                        }
                        valueString      = $"{valueString}% {lstValue} {MonStat.MonStats[parameter].NameStr}";
                        DescriptionValue = 3;
                        break;

                    case 24:
                        valueString = $"Level {value2} {Skill.GetSkill(parameter).Name} ({value} Charges)";
                        break;

                    case 27:
                        var charClass = Skill.GetSkill(parameter).CharClass;
                        var reqString = "";
                        if (!string.IsNullOrEmpty(charClass))
                        {
                            // Add requirement if one is there
                            if (!CharStat.CharStats.ContainsKey(Skill.GetSkill(parameter).CharClass))
                            {
                                throw ItemStatCostException.Create($"Could not find character skill tab '{Skill.GetSkill(parameter).CharClass}' property");
                            }
                            reqString = $" ({CharStat.CharStats[Skill.GetSkill(parameter).CharClass].Class} Only)";
                        }
                        valueString = $"+{valueString} to {Skill.GetSkill(parameter).Name}{reqString}";
                        break;

                    case 28:
                        valueString = $"+{valueString} to {Skill.GetSkill(parameter).Name}";
                        break;

                    case 29:     // Custom for sockets
                        if (string.IsNullOrEmpty(valueString))
                        {
                            valueString = parameter;
                        }

                        valueString = $"{lstValue} ({valueString})";
                        break;

                    case 30:     // Custom for elemental damage
                        valueString = lstValue.Replace("%d", valueString).Replace("%s", parameter);
                        break;

                    default:
                        // Not implemented function
                        valueString      = UnimplementedFunction(value, value2, parameter, Op, OpParam, DescriptionFunction.Value);
                        DescriptionValue = 3;
                        break;
                    }
                }
            }

            // Remove +- in case it happens
            valueString = valueString.Replace("+-", "-");

            if (DescriptionValue.HasValue && !string.IsNullOrEmpty(lstValue))
            {
                switch (DescriptionValue.Value)
                {
                case 0:
                    valueString = lstValue;
                    break;

                case 1:
                    valueString = $"{valueString} {lstValue}";
                    break;

                case 2:
                    valueString = $"{lstValue} {valueString}";
                    break;

                case 3:
                    // Used if the value already contain all information
                    break;
                }
            }

            // Trim whitespace and remove trailing newline as we sometimes see those in the properties
            valueString = valueString.Trim().Replace("\\n", "");
            return(valueString);
        }