// Copyright (C) Stichting Deltares 2026. All rights reserved.
//
// This file is part of the application DAM - UI.
//
// DAM - UI 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 .
//
// All names, logos, and references to "Deltares" are registered trademarks of
// Stichting Deltares and remain full property of Stichting Deltares at all times.
// All rights reserved.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Xml.Serialization;
using Deltares.Standard;
using Deltares.Standard.Attributes;
using Deltares.Standard.EventPublisher;
using Deltares.Standard.IO.Xml;
using Deltares.Standard.Specifications;
using NetTopologySuite.Geometries;
using XmlSerializer = Deltares.Standard.IO.Xml.XmlSerializer;
namespace Deltares.Dam.Data.Sensors;
///
/// This class represents a sensor or monitoring point used in dikes.
/// This entity is created for Dam Live
///
[Serializable]
public class Sensor : IName, IVisibleEnabled
{
private readonly NetTopologySuite.Geometries.Geometry geometry;
private readonly HashSet plLineTypes;
private double relativeLocation;
private int id;
private SensorType type;
public Sensor()
{
plLineTypes = new HashSet();
ID = -1;
geometry = new Point(0, 0, 0);
}
public Sensor(Point point) : this()
{
if (point == null)
{
throw new ArgumentNullException("point");
}
geometry = point;
}
public Sensor(double x, double y, double z) : this(new Point(x, y, z)) {}
///
/// Gets or sets the ID.
///
///
/// The ID should be unique.
///
[PropertyOrder(1, 1)]
public int ID
{
get
{
return id;
}
set
{
id = value;
DataEventPublisher.AfterChange(this, s => s.ID);
}
}
///
/// Gets or sets the depth.
///
///
/// The depth.
///
[PropertyOrder(1, 5)]
[Format("F3")]
public double Depth
{
get
{
return ZRd;
}
set
{
DataEventPublisher.BeforeChange(this, s => s.Depth);
ZRd = value;
DataEventPublisher.AfterChange(this, s => s.Depth);
}
}
///
/// Gets or sets the relative location along the profile.
///
///
/// The relative location in meter.
///
[PropertyOrder(1, 4)]
[Format("F3")]
public double RelativeLocation
{
get
{
return relativeLocation;
}
set
{
DataEventPublisher.BeforeChange(this, s => s.RelativeLocation);
relativeLocation = value;
RelativeLocationSpecified = true;
DataEventPublisher.AfterChange(this, s => s.RelativeLocation);
}
}
///
/// Gets or sets the X rd.
///
///
/// The X rd.
///
[Browsable(false)]
public double XRd
{
get
{
return geometry.Coordinate.X;
}
set
{
geometry.Coordinate.X = value;
}
}
///
/// Gets or sets the Y rd.
///
///
/// The Y rd.
///
[Browsable(false)]
public double YRd
{
get
{
return geometry.Coordinate.Y;
}
set
{
geometry.Coordinate.Y = value;
}
}
///
/// Gets or sets the Z rd. Same as Depth??
///
///
/// The Z rd.
///
[Browsable(false)]
public double ZRd
{
get
{
return geometry.Coordinate.Z;
}
set
{
geometry.Coordinate.Z = value;
}
}
///
/// Gets a value indicating whether the relative location is specified.
///
///
/// true if the relative location is specified; otherwise, false.
///
[Browsable(false)]
public bool RelativeLocationSpecified { get; private set; }
///
/// Gets or sets the type of this sensor.
///
///
/// The type. Default value is PiezoMetricHead.
///
[PropertyOrder(1, 7)]
public SensorType Type
{
get
{
return type;
}
set
{
type = value;
}
}
///
/// Gets or sets the PL line array. Used for serialization only.
///
///
/// The PL line array.
///
[XmlElement("PLLineMappings")]
[Specification(typeof(NotNullSpecification))]
[Specification(typeof(ContiansAtLeastOneItem))]
[Browsable(false)]
public PLLineType[] PLLineMappings
{
get
{
return plLineTypes.ToArray();
}
set
{
ClearPLLines();
foreach (PLLineType lineType in value)
{
Add(lineType);
}
}
}
[XmlIgnore]
[PropertyOrder(1, 6)]
public string PLLineMappingsAsString
{
get
{
var res = "";
foreach (PLLineType plLineType in plLineTypes)
{
switch (plLineType)
{
case PLLineType.PL1:
res = res + "1; ";
break;
case PLLineType.PL2:
res = res + "2; ";
break;
case PLLineType.PL3:
res = res + "3; ";
break;
case PLLineType.PL4:
res = res + "4; ";
break;
}
}
return res;
}
set
{
ClearPLLines();
List locPlLineTypes = ParseStringToPlLineTypes(value);
foreach (int plLineType in locPlLineTypes)
{
switch (plLineType)
{
case 1:
plLineTypes.Add(PLLineType.PL1);
break;
case 2:
plLineTypes.Add(PLLineType.PL2);
break;
case 3:
plLineTypes.Add(PLLineType.PL3);
break;
case 4:
plLineTypes.Add(PLLineType.PL4);
break;
}
}
}
}
///
/// Gets or sets the name of this sensor.
///
///
/// The name string value should not be empty and unique.
///
[Specification(typeof(NotEmptySpecification))]
[PropertyOrder(1, 2)]
public string Name { get; set; }
///
/// Adds the specified PL line.
///
/// The PL line.
public void Add(PLLineType plLine)
{
plLineTypes.Add(plLine);
}
///
/// Removes the specified PL line.
///
/// The PL line.
public void Remove(PLLineType plLine)
{
plLineTypes.Remove(plLine);
}
///
/// Clears the PL lines list.
///
public void ClearPLLines()
{
plLineTypes.Clear();
}
///
/// Determines whether this instance is valid.
///
///
/// true if this instance is valid; otherwise, false.
///
public bool IsValid()
{
return !Validator.Validate(this).Any();
}
///
/// Determines whether this instance is transient (associated with a correct ID in the context of the repository).
///
///
/// true if this instance is transient; otherwise, false.
///
public bool IsTransient()
{
return ID < 0;
}
public override string ToString()
{
string name = string.IsNullOrWhiteSpace(Name) ? "name_not_set" : Name;
return $"[ID: {ID}, Name: {name}, Depth: {Depth}, Type: {Type}, RelativeLocation: {RelativeLocation}]";
}
public bool IsVisible(string property)
{
switch (property)
{
case "XRd": return false;
case "YRd": return false;
case "ZRd": return false;
default: return true;
}
}
public bool IsEnabled(string property)
{
return true;
}
///
/// Serializes this instance to a xml string.
///
/// A xml string
internal string Serialize()
{
var xmlSerializer = new XmlSerializer();
return xmlSerializer.SerializeToString(this);
}
///
/// Deserializes the specified XML.
///
/// The XML to deserialize.
/// A sensor instance
internal static Sensor Deserialize(string xml)
{
var xmlDeserializer = new XmlDeserializer();
return (Sensor) xmlDeserializer.XmlDeserializeFromString(xml, typeof(Sensor));
}
private List ParseStringToPlLineTypes(string value)
{
value = value.Trim();
var ids = new List();
string[] idsarr = value.Split(new[]
{
';'
}, StringSplitOptions.RemoveEmptyEntries);
foreach (string s in idsarr)
{
try
{
int val = Int32.Parse(s);
ids.Add(val);
}
catch (Exception)
{
// surpress errors, just do not use value
}
}
return ids;
}
#region Business Rules
///
/// Specication to test if the value of the candidate is valid
///
internal class ContiansAtLeastOneItem : PredicateSpecification>
{
public ContiansAtLeastOneItem()
: base(x => x.Any())
{
Description = "The sensor should contain at least one PL Line mapping.";
}
}
#endregion
}