agrobot_base/AgroBase/OperationControl/Mapas/Services/MapaSelectionService.cs

417 lines
9.5 KiB
C#

using OperationControl.Mapas.Models;
namespace OperationControl.Mapas.Services
{
public class MapaSelectionService
{
/// <summary>
/// Alterna o estado de seleção de uma rua.
///
/// Retorna true se a rua ficou selecionada.
/// Retorna false se a rua ficou desmarcada.
/// </summary>
public bool AlternarRua(
MapaDocumento documento,
MapaSelecaoOperacao selecao,
string ruaId)
{
ValidarArgumentos(
documento,
selecao
);
string id =
NormalizarId(
ruaId
);
GarantirRuaExistente(
documento,
id
);
if (selecao.RuasMutaveis.Contains(
id,
StringComparer.OrdinalIgnoreCase))
{
RemoverRua(
documento,
selecao,
id
);
return false;
}
SelecionarRua(
documento,
selecao,
id
);
return true;
}
/// <summary>
/// Seleciona uma rua e a coloca no final da ordem atual.
///
/// Se já estiver selecionada, nada é alterado.
/// </summary>
public bool SelecionarRua(
MapaDocumento documento,
MapaSelecaoOperacao selecao,
string ruaId)
{
ValidarArgumentos(
documento,
selecao
);
string id =
NormalizarId(
ruaId
);
GarantirRuaExistente(
documento,
id
);
if (selecao.RuasMutaveis.Contains(
id,
StringComparer.OrdinalIgnoreCase))
{
return false;
}
selecao.RuasMutaveis.Add(
id
);
return true;
}
/// <summary>
/// Remove uma rua da seleção.
///
/// Como a ordem é representada pela própria posição
/// na lista, nenhuma renumeração manual é necessária.
/// </summary>
public bool RemoverRua(
MapaDocumento documento,
MapaSelecaoOperacao selecao,
string ruaId)
{
ValidarArgumentos(
documento,
selecao
);
string id =
NormalizarId(
ruaId
);
int indice =
selecao.RuasMutaveis.FindIndex(
x => string.Equals(
x,
id,
StringComparison.OrdinalIgnoreCase
)
);
if (indice < 0)
{
return false;
}
selecao.RuasMutaveis.RemoveAt(
indice
);
return true;
}
/// <summary>
/// Substitui a seleção atual por uma lista já ordenada.
///
/// Útil para restaurar uma operação previamente
/// parametrizada ou recebida de outro componente.
///
/// IDs inválidos, vazios ou duplicados não são aceitos.
/// </summary>
public void DefinirSelecao(
MapaDocumento documento,
MapaSelecaoOperacao selecao,
IEnumerable<string>? ruasOrdenadas)
{
ValidarArgumentos(
documento,
selecao
);
var novaSelecao =
new List<string>();
var idsUsados =
new HashSet<string>(
StringComparer.OrdinalIgnoreCase
);
foreach (
string? ruaId
in ruasOrdenadas ?? Enumerable.Empty<string>())
{
string id =
NormalizarId(
ruaId
);
GarantirRuaExistente(
documento,
id
);
if (!idsUsados.Add(id))
{
throw new InvalidOperationException(
$"A seleção contém o ID de rua '{id}' repetido."
);
}
novaSelecao.Add(
id
);
}
/*
* Só altera o objeto oficial depois que a nova
* seleção inteira foi validada.
*
* Assim a operação é transacional:
* se houver um ID inválido no meio da lista,
* a seleção anterior permanece intacta.
*/
selecao.RuasMutaveis.Clear();
selecao.RuasMutaveis.AddRange(
novaSelecao
);
}
public void Limpar(
MapaSelecaoOperacao selecao)
{
if (selecao == null)
{
throw new ArgumentNullException(
nameof(selecao)
);
}
selecao.RuasMutaveis.Clear();
}
public bool EstaSelecionada(
MapaSelecaoOperacao selecao,
string ruaId)
{
if (selecao == null)
{
throw new ArgumentNullException(
nameof(selecao)
);
}
string id =
NormalizarId(
ruaId
);
return selecao.RuasMutaveis.Contains(
id,
StringComparer.OrdinalIgnoreCase
);
}
/// <summary>
/// Retorna a ordem 1-based da rua.
///
/// Exemplo:
/// primeira selecionada = 1.
///
/// Retorna null caso a rua não esteja selecionada.
/// </summary>
public int? ObterOrdem(
MapaSelecaoOperacao selecao,
string ruaId)
{
if (selecao == null)
{
throw new ArgumentNullException(
nameof(selecao)
);
}
string id =
NormalizarId(
ruaId
);
int indice =
selecao.RuasMutaveis.FindIndex(
x => string.Equals(
x,
id,
StringComparison.OrdinalIgnoreCase
)
);
return indice < 0
? null
: indice + 1;
}
/// <summary>
/// Retorna uma cópia independente da seleção.
///
/// Este é o método que futuramente alimentará
/// OperacaoParametrosModel.RuasPercorrer.
/// </summary>
public List<string> ObterRuasOrdenadas(
MapaSelecaoOperacao selecao)
{
if (selecao == null)
{
throw new ArgumentNullException(
nameof(selecao)
);
}
return selecao.RuasOrdenadas
.ToList();
}
private static void GarantirRuaExistente(
MapaDocumento documento,
string ruaId)
{
int quantidade =
documento.Ruas.Count(
x =>
x != null &&
string.Equals(
x.Id,
ruaId,
StringComparison.OrdinalIgnoreCase
)
);
if (quantidade == 0)
{
throw new InvalidOperationException(
$"A rua '{ruaId}' não existe no mapa."
);
}
/*
* Isso jamais deveria acontecer depois que o
* MapaValidationService aprovou o documento.
*
* Mesmo assim protegemos esta fronteira.
*/
if (quantidade > 1)
{
throw new InvalidOperationException(
$"O mapa possui {quantidade} ruas com o ID '{ruaId}'."
);
}
}
private static string NormalizarId(
string? ruaId)
{
if (string.IsNullOrWhiteSpace(
ruaId))
{
throw new ArgumentException(
"O ID da rua não foi informado.",
nameof(ruaId)
);
}
return ruaId.Trim();
}
private static void ValidarArgumentos(
MapaDocumento documento,
MapaSelecaoOperacao selecao)
{
if (documento == null)
{
throw new ArgumentNullException(
nameof(documento)
);
}
if (selecao == null)
{
throw new ArgumentNullException(
nameof(selecao)
);
}
if (documento.Ruas == null ||
documento.Ruas.Count == 0)
{
throw new InvalidOperationException(
"O mapa não possui ruas para seleção."
);
}
}
}
}