using DevExpress.Web.Mvc;
using DevExpress.XtraReports.UI;
using GreenTree.Nachtragsmanagement.Core;
using GreenTree.Nachtragsmanagement.Core.Authentication;
using GreenTree.Nachtragsmanagement.Core.Domain.Appendix;
using GreenTree.Nachtragsmanagement.Core.Domain.Deviation;
using GreenTree.Nachtragsmanagement.Services.Appendix;
using GreenTree.Nachtragsmanagement.Services.Configuration;
using GreenTree.Nachtragsmanagement.Services.Deviation;
using GreenTree.Nachtragsmanagement.Services.Logging;
using GreenTree.Nachtragsmanagement.Services.Site;
using GreenTree.Nachtragsmanagement.Web.Extensions;
using GreenTree.Nachtragsmanagement.Web.Framework.Authorization;
using GreenTree.Nachtragsmanagement.Web.Models.Appendix;
using GreenTree.Nachtragsmanagement.Web.Models.Deviation;
using GreenTree.Nachtragsmanagement.Web.Models.Global;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Net.Mime;
using System.Web;
using System.Web.Mvc;
using static GreenTree.Nachtragsmanagement.Web.Extensions.MVCxGridViewGeneratorHelper;

namespace GreenTree.Nachtragsmanagement.Web.Controllers
{
    public class DeviationController : Controller
    {
        private readonly IDeviationService _deviationService;
        private readonly IAppendixService _appendixService;
        private readonly ISiteService _siteService;
        private readonly IUserHelper _userHelper;
        private readonly ILogger _logger;
        private readonly IConfigurationService _configurationService;

        public DeviationController(
            IDeviationService deviationService,
            IAppendixService appendixService,
            ISiteService siteService,
            IUserHelper userHelper,
            ILogger logger,
            IConfigurationService configurationService)
        {
            _deviationService = deviationService;
            _appendixService = appendixService;
            _siteService = siteService;
            _userHelper = userHelper;
            _logger = logger;
            _configurationService = configurationService;

            ViewData["AllDisturbances"] = _deviationService.GetAllDisturbances();
            ViewData["AllStatuses"] = _deviationService.GetAllStatuses();
            ViewData["AllKinds"] = _deviationService.GetAllKinds();
        }

        #region Deviations

        /// <summary>
        /// Basic deviation view function
        /// </summary>
        [FunctionAuthorize(true, "Deviation-Deviations")]
        public ActionResult ViewDeviations()
        {
            var currentUser = _userHelper.FromCookiesOrSession();

            var deviations = _deviationService.GetAllUserAssignedDeviations(currentUser);
            var deviationModels = deviations
                .Select(u => DeviationDataModel.FromDeviation(u, false, _configurationService))
                .ToList();

            return View("~/Views/Deviations/View.cshtml", deviationModels);
        }

        /// <summary>
        /// Get JSON data of specific deviation
        /// </summary>
        /// <param name="id">Deviation id.</param>
        public ActionResult GetDeviation(int id = -1)
        {
            var deviation = _deviationService.GetDeviationById(id);
            if (deviation == null)
                return new JsonResult
                {
                    Data = "notFound",
                    JsonRequestBehavior = JsonRequestBehavior.AllowGet
                };

            var deviationModel = DeviationDataModel.FromDeviation(deviation, false, _configurationService);

            return new JsonResult
            {
                Data = JsonConvert.SerializeObject(deviationModel),
                JsonRequestBehavior = JsonRequestBehavior.AllowGet
            };
        }

        /// <summary>
        /// Callback result for deviation grid
        /// </summary>
        /// <param name="scrollHeight">The height of the grid scrollable component.</param>
        public ActionResult PartialDeviations(string customFilters, int scrollHeight = -1)
        {
            var currentUser = _userHelper.FromCookiesOrSession();

            var deviations = _deviationService.GetAllUserAssignedDeviations(currentUser);
            var deviationModels = deviations
                .Select(u => DeviationDataModel.FromDeviation(u, false, _configurationService))
                .ToList();

            if (!String.IsNullOrEmpty(customFilters))
                UseCustomFilters(customFilters, deviationModels);

            ViewData["ScrollHeight"] = scrollHeight;

            return PartialView("~/Views/Deviations/_DeviationGridPartial.cshtml", deviationModels);
        }

        /// <summary>
        /// Export result for deviation grid
        /// </summary>
        [HttpPost]
        public ActionResult ExportPartialDeviations(string customFilters, string displayMode, string exportformat)
        {
            if (String.IsNullOrEmpty(displayMode))
                return new EmptyResult();

            var currentUser = _userHelper.FromCookiesOrSession();

            var deviations = _deviationService.GetAllUserAssignedDeviations(currentUser);
            var deviationModels = deviations
                .Select(u => DeviationDataModel.FromDeviation(u, false, _configurationService))
                .ToList();

            if (!String.IsNullOrEmpty(customFilters))
                UseCustomFilters(customFilters, deviationModels);

            var viewContext = new ViewContext();
            var viewPage = new ViewPage();
            var htmlHelper = new System.Web.Mvc.HtmlHelper(viewContext, viewPage);

            MVCxGridViewState gridViewState = (MVCxGridViewState)Session["DeviationGridViewState"];

            var settings = GridViewSettingsHelper.DeviationGridViewSettings(htmlHelper);

            if (gridViewState != null)
            {
                var generator = new MVCReportGeneratonHelper();

                generator.CustomizeFormattingRules += new CustomizeFormattingRulesEventHandler(generator_CustomizeFormattingRules);
                generator.CustomizeColumnsCollection += new CustomizeColumnsCollectionEventHandler(generator_CustomizeColumnsCollection);
                generator.CustomizeGroupColumnSummary += new CustomizeColumnGroupSummaryEventHandler(generator_CustomizeGroupColumnSummary);
                generator.CustomizeTotalColumnSummary += new CustomizeColumnTotalSummaryEventHandler(generator_CustomizeTotalColumnSummary);
                generator.SummaryReset += new SummaryResetEventHandler(generator_SummaryReset);
                generator.SummaryRowChanged += new SummaryRowChangedEventHandler(generator_SummaryRowChanged);
                generator.PageSummaryGetResult += new SummaryGetResultHandler(generator_PageSummaryGetResult);
                generator.TotalSummaryGetResult += new SummaryGetResultHandler(generator_TotalSummaryGetResult);

                var report = generator.GenerateMVCReport(gridViewState, deviationModels, "Vertragsabweichungsliste");

                if (displayMode == "popup")
                {
                    return PartialView("~/Views/Shared/_PrintPopupPartial.cshtml",
                        new PrintGridModel(report, "devGridViewDeviation",
                            new { Controller = "Deviation", Action = "ExportPartialDeviations",
                                customFilters = customFilters, displayMode = "callback", exportformat = String.Empty },
                            new { Controller = "Deviation", Action = "ExportPartialDeviations",
                                customFilters = customFilters, displayMode = "export", exportformat = String.Empty }));
                }
                else if (displayMode == "callback")
                {
                    return PartialView("~/Views/Shared/_PrintDocumentViewerPartial.cshtml",
                        new PrintGridModel(report, "devGridViewDeviation",
                            new { Controller = "Deviation", Action = "ExportPartialDeviations",
                                customFilters = customFilters, displayMode = "callback", exportformat = String.Empty },
                            new { Controller = "Deviation", Action = "ExportPartialDeviations",
                                customFilters = customFilters, displayMode = "export", exportformat = String.Empty }));
                }
                else if (displayMode == "export")
                {
                    switch (exportformat.ToLower())
                    {
                        case "xlsx":
                            settings.TotalSummary["Value"].DisplayFormat = "{0:c2}";
                            settings.TotalSummary["Percentage"].DisplayFormat = "{0:p0}";
                            settings.TotalSummary["PercentageValue"].DisplayFormat = "{0:c2}";
                            settings.TotalSummary["StatusDescription"].DisplayFormat = "{0:c2}";
                            settings.TotalSummary["DaysReceiptToAppendixOffering"].DisplayFormat = "Schnitt = {0:n0}";
                            settings.TotalSummary["SiteDescription"].DisplayFormat = "Anzahl = {0:n0}";
                            settings.TotalSummary["DisturbanceValuesDescription"].DisplayFormat = "{0:c2}";
                            return GridViewExtension.ExportToXlsx(settings, deviationModels);
                        case "xls":
                            settings.TotalSummary["Value"].DisplayFormat = "{0:c2}";
                            settings.TotalSummary["Percentage"].DisplayFormat = "{0:p0}";
                            settings.TotalSummary["PercentageValue"].DisplayFormat = "{0:c2}";
                            settings.TotalSummary["StatusDescription"].DisplayFormat = "{0:c2}";
                            settings.TotalSummary["DaysReceiptToAppendixOffering"].DisplayFormat = "Schnitt = {0:n0}";
                            settings.TotalSummary["SiteDescription"].DisplayFormat = "Anzahl = {0:n0}";
                            settings.TotalSummary["DisturbanceValuesDescription"].DisplayFormat = "{0:c2}";
                            return GridViewExtension.ExportToXls(settings, deviationModels);
                        case "pdf":
                            report.Name = "VA-Liste";
                            return DocumentViewerExtension.ExportTo(report);
                    }
                }

                return new EmptyResult();
            }
            else
                return new EmptyResult();
        }

        private decimal totalCustomSummaryValueDaysReceiptToAppendixOffering = 0;
        private decimal totalCustomSummaryValueDaysReceiptToAppendixOfferingCount = 0;
        private decimal totalCustomSummaryValueDisturbanceValueSum = 0;
        private decimal totalCustomSummaryPercentageValueZeroStatusSum = 0;

        private decimal accumulatedCustomSummaryValueDaysReceiptToAppendixOffering = 0;
        private int accumulatedCustomSummaryValueDaysReceiptToAppendixOfferingCount = 0;
        private decimal accumulatedCustomSummaryPercentage = 0;
        private int accumulatedCustomSummaryPercentageCount = 0;
        private decimal accumulatedCustomSummaryValueDisturbanceValueSum = 0;
        private decimal accumulatedCustomSummaryPercentageValue = 0;
        private decimal accumulatedCustomSummaryValue = 0;
        private decimal accumulatedCustomSummaryCount = 0;
        private decimal accumulatedCustomSummaryPercentageValueZeroStatusSum = 0;
        private List<decimal> accumulatedCustomSummaryPercentageValueZeroStatusSumList = new List<decimal>();
        private int accumulatedCustomSummaryPercentageValueZeroStatusIndex = 0;

        /// <summary>
        /// Reset custom summaries for corresponding group type
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        private void generator_SummaryReset(object source, CustomSummaryResetEventArgs e)
        {
            if (e.FieldName == "DaysReceiptToAppendixOffering")
            {
                totalCustomSummaryValueDaysReceiptToAppendixOffering = 0;
                totalCustomSummaryValueDaysReceiptToAppendixOfferingCount = 0;
            }

            if (e.FieldName == "DisturbanceValuesDescription")
                totalCustomSummaryValueDisturbanceValueSum = 0;

            if (e.FieldName == "StatusDescription")
            {
                totalCustomSummaryPercentageValueZeroStatusSum = 0;
                accumulatedCustomSummaryPercentageValueZeroStatusSum = 0;
                accumulatedCustomSummaryPercentageValueZeroStatusSumList.Clear();
                accumulatedCustomSummaryPercentageValueZeroStatusIndex = 0;
            }
        }

        /// <summary>
        /// Get custom summary values
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        private void generator_SummaryRowChanged(object source, CustomSummaryRowChangedEventArgs e)
        {
            if (e.FieldName == "DaysReceiptToAppendixOffering")
            {
                var val = ((XtraReport)source).GetCurrentColumnValue("DaysReceiptToAppendixOffering");

                if (val != null)
                {
                    totalCustomSummaryValueDaysReceiptToAppendixOffering += Convert.ToDecimal(val);
                    totalCustomSummaryValueDaysReceiptToAppendixOfferingCount++;
                }
            }

            if (e.FieldName == "DisturbanceValuesDescription")
            {
                totalCustomSummaryValueDisturbanceValueSum +=
                    Convert.ToDecimal(((XtraReport)source).GetCurrentColumnValue("DisturbanceValueSum"));
            }

            if (e.FieldName == "StatusDescription")
            {
                var isZeroValueSum = ((XtraReport)source).GetCurrentColumnValue("IsZeroSummarize");

                if (isZeroValueSum == null || (isZeroValueSum != null && !Convert.ToBoolean(isZeroValueSum)))
                {
                    var percentageValue = ((XtraReport)source).GetCurrentColumnValue("PercentageValue");

                    if (percentageValue != null)
                    {
                        totalCustomSummaryPercentageValueZeroStatusSum += (decimal)percentageValue;
                        accumulatedCustomSummaryPercentageValueZeroStatusSumList.Add((decimal)percentageValue);
                    }
                    else
                        accumulatedCustomSummaryPercentageValueZeroStatusSumList.Add(0);
                }
                else
                    accumulatedCustomSummaryPercentageValueZeroStatusSumList.Add(0);
            }
        }

        /// <summary>
        /// Set custom summary result for page
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        private void generator_PageSummaryGetResult(object source, SummaryGetResultEventArgs e)
        {
            var label = (XRLabel)source;

            if (label.Tag.ToString() == "DaysReceiptToAppendixOffering")
            {
                accumulatedCustomSummaryValueDaysReceiptToAppendixOffering += e.CalculatedValues.OfType<int?>()
                    .Where(c => c.HasValue)
                    .Select(c => Convert.ToDecimal(c))
                    .Sum();
                accumulatedCustomSummaryValueDaysReceiptToAppendixOfferingCount += e.CalculatedValues.OfType<int?>()
                    .Where(c => c.HasValue)
                    .Count();
                e.Result = accumulatedCustomSummaryValueDaysReceiptToAppendixOffering / 
                           (accumulatedCustomSummaryValueDaysReceiptToAppendixOfferingCount == 0 
                                ? 1 
                                : accumulatedCustomSummaryValueDaysReceiptToAppendixOfferingCount);
            }

            if (label.Tag.ToString() == "DisturbanceValuesDescription")
            {
                var vals = e.CalculatedValues.OfType<string>()
                    .Where(s => !String.IsNullOrEmpty(s))
                    .SelectMany(s => s.Split(new[] { ", " }, StringSplitOptions.None))
                    .Select(s => s.Split(new[] { " - " }, StringSplitOptions.None)[1])
                    .Select(s => s.Take(s.Length - 2))
                    .Select(c => String.Join("", c))
                    .ToArray();

                accumulatedCustomSummaryValueDisturbanceValueSum += vals
                    .Select(v => Convert.ToDecimal(v))
                    .Sum();

                e.Result = accumulatedCustomSummaryValueDisturbanceValueSum;
            }

            if (label.Tag.ToString() == "Value")
            {
                accumulatedCustomSummaryValue += e.CalculatedValues.OfType<decimal>().Sum();
                e.Result = accumulatedCustomSummaryValue;
            }

            if (label.Tag.ToString() == "SiteDescription")
            {
                accumulatedCustomSummaryCount += e.CalculatedValues.Count;
                e.Result = accumulatedCustomSummaryCount;
            }

            if (label.Tag.ToString() == "Percentage")
            {
                accumulatedCustomSummaryPercentage += e.CalculatedValues.OfType<decimal>().Sum();
                accumulatedCustomSummaryPercentageCount += e.CalculatedValues.Count;
                e.Result = accumulatedCustomSummaryPercentage /
                           ((accumulatedCustomSummaryPercentageCount == 0
                                ? 1
                                : accumulatedCustomSummaryPercentage));
            }

            if (label.Tag.ToString() == "PercentageValue")
            {
                accumulatedCustomSummaryPercentageValue += e.CalculatedValues.OfType<decimal>().Sum();
                e.Result = accumulatedCustomSummaryPercentageValue;
            }

            if (label.Tag.ToString() == "StatusDescription")
            {
                accumulatedCustomSummaryPercentageValueZeroStatusSum +=
                    accumulatedCustomSummaryPercentageValueZeroStatusSumList
                        .Skip(accumulatedCustomSummaryPercentageValueZeroStatusIndex)
                        .Take(e.CalculatedValues.Count)
                        .Sum();

                e.Result = accumulatedCustomSummaryPercentageValueZeroStatusSum;

                accumulatedCustomSummaryPercentageValueZeroStatusIndex += e.CalculatedValues.Count;
            }

            e.Handled = true;
        }

        /// <summary>
        /// Set custom summary result for full report
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        private void generator_TotalSummaryGetResult(object source, SummaryGetResultEventArgs e)
        {
            var label = (XRLabel)source;

            if (label.Tag.ToString() == "DaysReceiptToAppendixOffering")
            {
                e.Result = Convert.ToInt32(totalCustomSummaryValueDaysReceiptToAppendixOffering /
                                           (totalCustomSummaryValueDaysReceiptToAppendixOfferingCount == 0
                                                ? 1
                                                : totalCustomSummaryValueDaysReceiptToAppendixOfferingCount));
                e.Handled = true;
            }

            if (label.Tag.ToString() == "DisturbanceValuesDescription")
            {
                e.Result = totalCustomSummaryValueDisturbanceValueSum;
                e.Handled = true;
            }

            if (label.Tag.ToString() == "StatusDescription")
            {
                e.Result = totalCustomSummaryPercentageValueZeroStatusSum;
                e.Handled = true;
            }
        }

        /// <summary>
        /// Customize formatting
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        private void generator_CustomizeFormattingRules(object source, CustomFormattingRulesEventArgs e)
        {
            var colorLevel1 = _configurationService.TryGetConfigItemValue<string>(
                "GreenTree.Nachtragsmanagement.DeviationNotificationPlugin.ProcessDeviationReceipt.HexColorLevel1", "#FFD800");
            var ageDaysLevel1 = _configurationService.TryGetConfigItemValue<int>(
                "GreenTree.Nachtragsmanagement.DeviationNotificationPlugin.ProcessDeviationReceipt.AgeDaysLevel1", 40);

            var colorLevel2 = _configurationService.TryGetConfigItemValue<string>(
                "GreenTree.Nachtragsmanagement.DeviationNotificationPlugin.ProcessDeviationReceipt.HexColorLevel2", "#FF6A00");
            var ageDaysLevel2 = _configurationService.TryGetConfigItemValue<int>(
                "GreenTree.Nachtragsmanagement.DeviationNotificationPlugin.ProcessDeviationReceipt.AgeDaysLevel2", 60);

            var ageDaysLevel1Rule = new FormattingRule
            {
                Condition = String.Format("[DaysReceiptDateToday] >= {0}", ageDaysLevel1),
                Name = "AgeDaysLevel1BackColor"
            };

            ageDaysLevel1Rule.Formatting.BackColor = ColorTranslator.FromHtml(colorLevel1);

            var ageDaysLevel2Rule = new FormattingRule
            {
                Condition = String.Format("[DaysReceiptDateToday] >= {0}", ageDaysLevel2),
                Name = "AgeDaysLevel2BackColor"
            };

            ageDaysLevel2Rule.Formatting.BackColor = ColorTranslator.FromHtml(colorLevel2);

            e.Rules.Add(ageDaysLevel1Rule);
            e.Rules.Add(ageDaysLevel2Rule);
        }

        /// <summary>
        /// Customize created columns
        /// </summary>
        private void generator_CustomizeColumnsCollection(object source, ColumnsCreationEventArgs e)
        {
            foreach (var column in e.ColumnsInfo)
            {
                if (column.FieldName == "CustomNumber") { column.ColumnWidth = 30; }
                if (column.FieldName == "Description") { column.IsVisible = false; column.IsDetail = true; }
                if (column.FieldName == "SiteCustomNumber") { column.IsVisible = false; column.IsDetail = true; }
                if (column.FieldName == "SiteDescription") { column.IsVisible = false; column.IsDetail = true; }
                if (column.FieldName == "UserDescription") { column.IsVisible = false; column.IsDetail = true; }
                if (column.FieldName == "AppendixDescription") { column.ColumnWidth = 50; }
                if (column.FieldName == "StatusDescription") { column.ColumnWidth = 60; }
            }
        }

        /// <summary>
        /// Customize column summary
        /// </summary>
        private void generator_CustomizeGroupColumnSummary(object source, ColumnSummaryCreationEventArgs e)
        {
            if (e.FieldName == "SiteDescription") { e.Summary.FormatString = "Alle = {0:n0}"; }
            if (e.FieldName == "Value") { e.Summary.FormatString = "Schätzung ∑ = {0:c2}"; }
            if (e.FieldName == "Percentage") { e.Summary.FormatString = "Bew. Ø = {0:p0}"; }
            if (e.FieldName == "PercentageValue") { e.Summary.FormatString = "S. Bew. ∑ = {0:c2}"; }
            if (e.FieldName == "StatusDescription") { e.Summary.FormatString = "S. o. Sta. ∑ = {0:c2}"; }
            if (e.FieldName == "DaysReceiptToAppendixOffering") { e.Summary.FormatString = "Tage VA-NT = {0:n0}"; }
            if (e.FieldName == "DisturbanceValuesDescription") { e.Summary.FormatString = "Kat. ∑ = {0:c2}"; }
        }

        /// <summary>
        /// Customize column summary
        /// </summary>
        private void generator_CustomizeTotalColumnSummary(object source, ColumnSummaryCreationEventArgs e)
        {
            if (e.FieldName == "SiteDescription") { e.Summary.FormatString = "Alle = {0:n0}"; }
            if (e.FieldName == "Value") { e.Summary.FormatString = "{0:c2}"; }
            if (e.FieldName == "Percentage") { e.Summary.FormatString = "{0:p0}"; }
            if (e.FieldName == "PercentageValue") { e.Summary.FormatString = "{0:c2}"; }
            if (e.FieldName == "StatusDescription") { e.Summary.FormatString = "{0:c2}"; }
            if (e.FieldName == "DaysReceiptToAppendixOffering") { e.Summary.FormatString = "{0:n0}"; }
            if (e.FieldName == "DisturbanceValuesDescription") { e.Summary.FormatString = "{0:c2}"; }
        }

        /// <summary>
        /// Partial edit for editing of existing or for new deviation
        /// </summary>
        /// <param name="id">Id for existing deviation, otherweise -1.</param>
        public ActionResult EditDeviation(int id = -1)
        {
            var deviation = _deviationService.GetDeviationById(id);
            var deviationModel = DeviationDataModel.FromDeviation(deviation, true, _configurationService);

            var defaultKind = _deviationService.GetDefaultKind();
            var defaultStatus = _deviationService.GetDefaultStatus();

            if (defaultKind != null)
                ViewData["DefaultKind"] = defaultKind.Id;

            if (defaultStatus != null)
                ViewData["DefaultStatus"] = defaultStatus.Id;

            return PartialView("~/Views/Deviations/_DeviationEditPartial.cshtml", deviationModel);
        }

        /// <summary>
        /// Partial edit for creating a new deviation for a site
        /// </summary>
        /// <param name="siteId">Id of the site which the deviation should be appended to.</param>
        public ActionResult AppendDeviationToSite(int siteId)
        {
            var site = _siteService.GetSiteById(siteId);
            var allDeviations = new List<Deviation>();

            allDeviations.AddRange(
                site.Appendices
                    .SelectMany(a => a.Deviations));

            allDeviations.AddRange(
                site.Deviations);

            var lastCustomNumber = 0;

            if (allDeviations.Any())
                lastCustomNumber = allDeviations
                    .Max(d => StaticHelper.TryParseInt(d.CustomNumber));

            var deviationModel = new DeviationDataModel
            {
                Id = -1,
                SiteId = siteId,
                CustomNumber = (lastCustomNumber + 1).ToString(),
                Percentage = 1,
            };

            var defaultKind = _deviationService.GetDefaultKind();
            var defaultStatus = _deviationService.GetDefaultStatus();

            if (defaultKind != null)
                ViewData["DefaultKind"] = defaultKind.Id;

            if (defaultStatus != null)
                ViewData["DefaultStatus"] = defaultStatus.Id;

            return PartialView("~/Views/Deviations/_DeviationEditPartial.cshtml", deviationModel);
        }

        /// <summary>
        /// Partial edit for creating a new deviation for an appendix
        /// </summary>
        /// <param name="appendixId">Id of the appendix which the deviation should be appended to.</param>
        public ActionResult AppendDeviationToAppendix(int appendixId)
        {
            var appendix = _appendixService.GetAppendixById(appendixId);
            var site = appendix.Site;

            var allDeviations = new List<Deviation>();

            allDeviations.AddRange(
                site.Appendices
                    .SelectMany(a => a.Deviations));

            allDeviations.AddRange(
                site.Deviations);

            var lastCustomNumber = allDeviations
                .Max(d => StaticHelper.TryParseInt(d.CustomNumber));

            var deviationModel = new DeviationDataModel
            {
                Id = -1,
                AppendixId = appendixId,
                CustomNumber = (lastCustomNumber + 1).ToString(),
                Percentage = 1,
            };

            var defaultKind = _deviationService.GetDefaultKind();
            var defaultStatus = _deviationService.GetDefaultStatus();

            if (defaultKind != null)
                ViewData["DefaultKind"] = defaultKind.Id;

            if (defaultStatus != null)
                ViewData["DefaultStatus"] = defaultStatus.Id;

            return PartialView("~/Views/Deviations/_DeviationEditPartial.cshtml", deviationModel);
        }

        /// <summary>
        /// Partial Edit result for assigning a deviation to a new appendix
        /// </summary>
        /// <param name="id">Id of deviation.</param>
        public ActionResult AssignDeviationToAppendix(int id)
        {
            var deviation = _deviationService.GetDeviationById(id);
            var availableAppendices = new List<Appendix>();

            if (deviation.Site != null)
                availableAppendices.AddRange(
                    deviation.Site.Appendices);
            else if (deviation.Appendix != null && deviation.Appendix.Site != null)
            {
                availableAppendices.AddRange(
                    deviation.Appendix.Site.Appendices
                        .Where(a => a.Id != deviation.AppendixId));
            }

            var appendixModels = availableAppendices
                .Select(a => AppendixDataModel.FromAppendix(a, false))
                .ToList();

            if (deviation.Appendix != null && deviation.Appendix.Site != null)
                appendixModels.Insert(0, new AppendixDataModel { Id = -1, Description = "Keinem (Als \"Offen\" markieren)" });

            ViewData["DeviationId"] = id;

            return PartialView("~/Views/Deviations/_DeviationAssignPartial.cshtml", appendixModels);
        }

        /// <summary>
        /// Assigns a deviation to the specified appendix 
        /// </summary>
        /// <param name="id">Id of deviation.</param>
        [HttpPost]
        public ActionResult AssignDeviation(int id, int appendixId)
        {
            try
            {
                var deviation = _deviationService.GetDeviationById(id);

                if (appendixId == -1 && deviation.Appendix != null && deviation.Appendix.Site != null)
                {
                    deviation.SiteId = deviation.Appendix.SiteId;
                    deviation.Appendix = null;
                    deviation.AppendixId = null;
                }
                else
                {
                    deviation.AppendixId = appendixId;
                    deviation.Site = null;
                    deviation.SiteId = null;
                }

                _deviationService.UpdateDeviation(deviation);

                return new JsonResult
                {
                    Data = "success"
                };
            }
            catch (Exception ex)
            {
                _logger.Error("Fehler bei Zuweisung einer Vertragsabweichung zu einem Nachtrag.", ex, _userHelper.FromCookiesOrSession());

                return PartialView("~/Views/Shared/_PopupError.cshtml", ex);
            }
        }

        /// <summary>
        /// Partial edit result if ModelState is valid, otherwise simple JSON result for success
        /// </summary>
        /// <param name="deviationModel">Deviation model to be saved.</param>
        [HttpPost, ValidateInput(false)]
        public ActionResult EditDeviation(DeviationDataModel deviationModel)
        {
            try
            {
                deviationModel.DisturbanceValueEntities =
                    deviationModel.DisturbanceEntities
                        .Select(r => JsonConvert.DeserializeObject<DisturbanceValueDataModel>(r))
                        .ToList();

                for (int i = 0; i < deviationModel.DisturbanceValueEntities.Count; i++)
                    deviationModel.DisturbanceValueEntities.ElementAt(i).Json = deviationModel.DisturbanceEntities.ElementAt(i);

                deviationModel.PercentageValue = deviationModel.Value * deviationModel.Percentage;

                if (!ModelState.IsValid)
                    return PartialView("~/Views/Deviations/_DeviationEditPartial.cshtml", deviationModel);

                var disturbanceValues = deviationModel.DisturbanceValueEntities
                    .Select(r => r.ToDisturbanceValue())
                    .ToList();

                if (deviationModel.Id == -1)
                {
                    var deviation = deviationModel.ToDeviation();

                    deviation.SetDisturbanceValues(disturbanceValues);

                    _deviationService.InsertDeviation(deviation);

                    _logger.Entity(deviation, Core.Domain.Logging.LogEntityActivity.Insert, _userHelper.FromCookiesOrSession());
                }
                else
                {
                    var deviation = _deviationService.GetDeviationById(deviationModel.Id);

                    deviation.CustomNumber = deviationModel.CustomNumber;
                    deviation.Description = deviationModel.Description;
                    deviation.ReceiptDate = deviationModel.ReceiptDate;
                    deviation.AppendixDate = deviationModel.AppendixDate;
                    deviation.Percentage = deviationModel.Percentage;
                    deviation.Value = deviationModel.Value;
                    deviation.AppendixId = deviationModel.AppendixId;
                    deviation.StatusId = deviationModel.StatusId;
                    deviation.KindId = deviationModel.KindId;
                    deviation.Comment = deviationModel.Comment;

                    deviation.SetDisturbanceValues(disturbanceValues);

                    _deviationService.UpdateDeviation(deviation);

                    _logger.Entity(deviation, Core.Domain.Logging.LogEntityActivity.Update, _userHelper.FromCookiesOrSession());
                }

                return new JsonResult
                {
                    Data = "success"
                };
            }
            catch (Exception ex)
            {
                _logger.Error("Fehler bei Speicherung einer Vertragsabweichung.", ex, _userHelper.FromCookiesOrSession());

                return PartialView("~/Views/Shared/_PopupError.cshtml", ex);
            }
        }

        /// <summary>
        /// Simple JSON result for deleting a specific deviation
        /// </summary>
        /// <param name="id">Deviation id.</param>
        [HttpPost]
        public ActionResult DeleteDeviation(int id)
        {
            try
            {
                var deviation = _deviationService.GetDeviationById(id);

                if (deviation != null)
                    _deviationService.DeleteDeviation(deviation);

                _logger.Entity(deviation, Core.Domain.Logging.LogEntityActivity.Delete, _userHelper.FromCookiesOrSession());

                return new JsonResult
                {
                    Data = "success"
                };
            }
            catch (Exception ex)
            {
                _logger.Error("Fehler bei Löschung einer Vertragsabweichung.", ex, _userHelper.FromCookiesOrSession());

                return PartialView("~/Views/Shared/_PopupError.cshtml", ex);
            }
        }

        #endregion

        #region Claims

        /// <summary>
        /// Basic claim view function
        /// </summary>
        [FunctionAuthorize(true, "Deviation-Claims")]
        public ActionResult ViewClaims()
        {
            return View("~/Views/Deviations/Claims.cshtml");
        }

        /// <summary>
        /// Get JSON data of specific claim
        /// </summary>
        /// <param name="claimType">Claim type.</param>
        /// <param name="id">Claim id.</param>
        public ActionResult GetClaim(string claimType, int id = -1)
        {
            switch (claimType.ToLower())
            {
                case "status":
                    var status = _deviationService.GetStatusById(id);
                    if (status == null)
                        return new JsonResult { Data = "notFound", JsonRequestBehavior = JsonRequestBehavior.AllowGet };
                    else
                        return new JsonResult {
                            Data = JsonConvert.SerializeObject(status),
                            JsonRequestBehavior = JsonRequestBehavior.AllowGet
                        };
                case "disturbance":
                    var disturbance = _deviationService.GetDisturbanceById(id);
                    if (disturbance == null)
                        return new JsonResult { Data = "notFound", JsonRequestBehavior = JsonRequestBehavior.AllowGet };
                    else
                        return new JsonResult
                        {
                            Data = JsonConvert.SerializeObject(disturbance),
                            JsonRequestBehavior = JsonRequestBehavior.AllowGet
                        };
                case "kind":
                    var kind = _deviationService.GetKindById(id);
                    if (kind == null)
                        return new JsonResult { Data = "notFound", JsonRequestBehavior = JsonRequestBehavior.AllowGet };
                    else
                        return new JsonResult
                        {
                            Data = JsonConvert.SerializeObject(kind),
                            JsonRequestBehavior = JsonRequestBehavior.AllowGet
                        };
                default:
                    return new JsonResult { Data = "unknownClaimType", JsonRequestBehavior = JsonRequestBehavior.AllowGet };
            }
        }

        /// <summary>
        /// Callback result for claim grid
        /// </summary>
        /// <param name="claimType">Claim type.</param>
        public ActionResult PartialClaims(string claimType)
        {
            switch (claimType.ToLower())
            {
                case "status":
                    return PartialView("~/Views/Deviations/_StatusListPartial.cshtml", ViewData["AllStatuses"]);
                case "disturbance":
                    return PartialView("~/Views/Deviations/_DisturbanceListPartial.cshtml", ViewData["AllDisturbances"]);
                case "kind":
                    return PartialView("~/Views/Deviations/_KindListPartial.cshtml", ViewData["AllKinds"]);
                default:
                    return new EmptyResult();
            }
        }

        /// <summary>
        /// Partial edit for editing of existing or for new claim
        /// </summary>
        /// <param name="claimType">Claim type.</param>
        /// <param name="id">Id for existing claim, otherweise -1.</param>
        public ActionResult EditClaim(string claimType = "", int id = -1)
        {
            switch (claimType.ToLower())
            {
                case "status":
                    var status = _deviationService.GetStatusById(id);
                    var statusModel = StatusDataModel.FromStatus(status, true);

                    return PartialView("~/Views/Deviations/_StatusEditPartial.cshtml", statusModel);
                case "disturbance":
                    var disturbance = _deviationService.GetDisturbanceById(id);
                    var disturbanceModel = DisturbanceDataModel.FromDisturbance(disturbance, true);

                    return PartialView("~/Views/Deviations/_DisturbanceEditPartial.cshtml", disturbanceModel);
                case "kind":
                    var kind = _deviationService.GetKindById(id);
                    var kindModel = KindDataModel.FromKind(kind, true);

                    return PartialView("~/Views/Deviations/_KindEditPartial.cshtml", kindModel);
                default:
                    return new EmptyResult();
            }
        }

        /// <summary>
        /// Partial edit result if ModelState is valid, otherwise simple JSON result for success
        /// </summary>
        /// <param name="statusModel">Status model to be saved.</param>
        [HttpPost, ValidateInput(false)]
        public ActionResult EditStatus(StatusDataModel statusModel)
        {
            try
            {
                if (!ModelState.IsValid)
                    return PartialView("~/Views/Deviations/_StatusEditPartial.cshtml", statusModel);

                var allStatuses = _deviationService.GetAllStatuses();

                if (statusModel.IsDefault)
                {
                    foreach (var status in allStatuses)
                    {
                        status.IsDefault = false;

                        _deviationService.UpdateStatus(status);
                    }
                }

                if (statusModel.Id == -1)
                {
                    var claim = statusModel.ToStatus();

                    _deviationService.InsertStatus(claim);

                    _logger.Entity(claim, Core.Domain.Logging.LogEntityActivity.Insert, _userHelper.FromCookiesOrSession());
                }
                else
                {
                    var claim = _deviationService.GetStatusById(statusModel.Id);

                    claim.Description = statusModel.Description;
                    claim.IsDefault = statusModel.IsDefault;
                    claim.IsZeroValue = statusModel.IsZeroValue;
                    claim.IsZeroSummarize = statusModel.IsZeroSummarize;

                    _deviationService.UpdateStatus(claim);

                    _logger.Entity(claim, Core.Domain.Logging.LogEntityActivity.Update, _userHelper.FromCookiesOrSession());
                }

                return new JsonResult
                {
                    Data = "success"
                };
            }
            catch (Exception ex)
            {
                _logger.Error("Fehler bei Speicherung eines VA-Status.", ex, _userHelper.FromCookiesOrSession());

                return PartialView("~/Views/Shared/_PopupError.cshtml", ex);
            }
        }

        /// <summary>
        /// Partial edit result if ModelState is valid, otherwise simple JSON result for success
        /// </summary>
        /// <param name="disturbanceModel">Disturbance model to be saved.</param>
        [HttpPost, ValidateInput(false)]
        public ActionResult EditDisturbance(DisturbanceDataModel disturbanceModel)
        {
            try
            {
                if (!ModelState.IsValid)
                    return PartialView("~/Views/Deviations/_DisturbanceEditPartial.cshtml", disturbanceModel);

                if (disturbanceModel.Id == -1)
                {
                    var claim = disturbanceModel.ToDisturbance();

                    _deviationService.InsertDisturbance(claim);

                    _logger.Entity(claim, Core.Domain.Logging.LogEntityActivity.Insert, _userHelper.FromCookiesOrSession());
                }
                else
                {
                    var disturbance = _deviationService.GetDisturbanceById(disturbanceModel.Id);

                    disturbance.Description = disturbanceModel.Description;

                    _deviationService.UpdateDisturbance(disturbance);

                    _logger.Entity(disturbance, Core.Domain.Logging.LogEntityActivity.Update, _userHelper.FromCookiesOrSession());
                }

                return new JsonResult
                {
                    Data = "success"
                };
            }
            catch (Exception ex)
            {
                _logger.Error("Fehler bei Speicherung einer VA-Kategorie.", ex, _userHelper.FromCookiesOrSession());

                return PartialView("~/Views/Shared/_PopupError.cshtml", ex);
            }
        }

        /// <summary>
        /// Partial edit result if ModelState is valid, otherwise simple JSON result for success
        /// </summary>
        /// <param name="kindModel">Kind model to be saved.</param>
        [HttpPost, ValidateInput(false)]
        public ActionResult EditKind(KindDataModel kindModel)
        {
            try
            {
                if (!ModelState.IsValid)
                    return PartialView("~/Views/Deviations/_KindEditPartial.cshtml", kindModel);

                var allKinds = _deviationService.GetAllKinds();

                if (kindModel.IsDefault)
                {
                    foreach (var kind in allKinds)
                    {
                        kind.IsDefault = false;

                        _deviationService.UpdateKind(kind);
                    }
                }

                if (kindModel.Id == -1)
                {
                    var claim = kindModel.ToKind();

                    _deviationService.InsertKind(claim);

                    _logger.Entity(claim, Core.Domain.Logging.LogEntityActivity.Insert, _userHelper.FromCookiesOrSession());
                }
                else
                {
                    var kind = _deviationService.GetKindById(kindModel.Id);

                    kind.Description = kindModel.Description;
                    kind.IsDefault = kindModel.IsDefault;

                    _deviationService.UpdateKind(kind);

                    _logger.Entity(kind, Core.Domain.Logging.LogEntityActivity.Update, _userHelper.FromCookiesOrSession());
                }

                return new JsonResult
                {
                    Data = "success"
                };
            }
            catch (Exception ex)
            {
                _logger.Error("Fehler bei Speicherung einer Art.", ex, _userHelper.FromCookiesOrSession());

                return PartialView("~/Views/Shared/_PopupError.cshtml", ex);
            }
        }

        /// <summary>
        /// Simple JSON result for deleting a specific claim
        /// </summary>
        /// <param name="claimType">Claim type.</param>
        /// <param name="id">Claim id.</param>
        /// <param name="replaceId">Id of claim which deviations get in place of deleting claim.</param>
        [HttpPost]
        public ActionResult DeleteClaim(string claimType, int id, int replaceId)
        {
            switch (claimType.ToLower())
            {
                case "status":
                    try
                    {
                        var status = _deviationService.GetStatusById(id);
                        var replaceStatus = _deviationService.GetStatusById(replaceId);

                        var statusDeviations = _deviationService.GetDeviationsByStatus(id);

                        foreach (var deviation in statusDeviations)
                        {
                            deviation.StatusId = replaceId;
                            deviation.Status = replaceStatus;

                            _deviationService.UpdateDeviation(deviation);
                        }

                        if (status != null)
                            _deviationService.DeleteStatus(status);

                        _logger.Entity(status, Core.Domain.Logging.LogEntityActivity.Delete, _userHelper.FromCookiesOrSession());
                    }
                    catch (Exception ex)
                    {
                        _logger.Error("Fehler bei Löschung eines VA-Status.", ex, _userHelper.FromCookiesOrSession());

                        return PartialView("~/Views/Shared/_PopupError.cshtml", ex);
                    }
                    break;
                case "kind":
                    try
                    {
                        var kind = _deviationService.GetKindById(id);
                        var replaceKind = _deviationService.GetKindById(replaceId);

                        var kindDeviations = _deviationService.GetDeviationsByKind(id);

                        foreach (var deviation in kindDeviations)
                        {
                            deviation.KindId = replaceId;
                            deviation.Kind = replaceKind;

                            _deviationService.UpdateDeviation(deviation);
                        }

                        if (kind != null)
                            _deviationService.DeleteKind(kind);

                        _logger.Entity(kind, Core.Domain.Logging.LogEntityActivity.Delete, _userHelper.FromCookiesOrSession());
                    }
                    catch (Exception ex)
                    {
                        _logger.Error("Fehler bei Löschung einer Art.", ex, _userHelper.FromCookiesOrSession());

                        return PartialView("~/Views/Shared/_PopupError.cshtml", ex);
                    }
                    break;
                case "disturbance":
                    try
                    {
                        var disturbance = _deviationService.GetDisturbanceById(id);
                        var replaceDisturbance = _deviationService.GetDisturbanceById(replaceId);

                        var allDeviations = _deviationService.GetAllDeviations();

                        foreach (var deviation in allDeviations)
                        {
                            foreach (var disturbanceValue in deviation.DisturbanceValues)
                            {
                                if (disturbanceValue.DisturbanceId == id)
                                {
                                    disturbanceValue.Disturbance = replaceDisturbance;
                                    disturbanceValue.DisturbanceId = replaceDisturbance.Id;
                                }
                            }

                            _deviationService.UpdateDeviation(deviation);
                        }

                        if (disturbance != null)
                            _deviationService.DeleteDisturbance(disturbance);

                        _logger.Entity(disturbance, Core.Domain.Logging.LogEntityActivity.Delete, _userHelper.FromCookiesOrSession());
                    }
                    catch (Exception ex)
                    {
                        _logger.Error("Fehler bei Löschung einer VA-Kategorie.", ex, _userHelper.FromCookiesOrSession());

                        return PartialView("~/Views/Shared/_PopupError.cshtml", ex);
                    }
                    break;
                default:
                    return new EmptyResult();
            }

            return new JsonResult
            {
                Data = "success"
            };
        }

        #endregion

        #region Helper

        /// <summary>
        /// Uses a customFilter on a collection of deviation data models
        /// </summary>
        /// <param name="customFilters">Custom filter string.</param>
        /// <param name="models">Model list.</param>
        private void UseCustomFilters(string customFilters, List<DeviationDataModel> models)
        {
            var filters = JsonConvert.DeserializeObject(customFilters) as JObject;

            if (filters["DisturbanceValuesDescription"] != null && !String.IsNullOrEmpty(filters["DisturbanceValuesDescription"].Value<string>()))
            {
                var removables = new List<DeviationDataModel>();
                var filterVal = filters["DisturbanceValuesDescription"].Value<string>();

                foreach (var model in models)
                {
                    var filterItemExists = model.DisturbanceValueEntities
                        .Any(d => d.Description.ToLower().Contains(filterVal.ToLower()));

                    if (!filterItemExists)
                        removables.Add(model);
                    else
                    {
                        var removeableValues = model.DisturbanceValueEntities
                            .Where(d => !d.Description.ToLower().Contains(filterVal.ToLower()))
                            .ToList();

                        foreach (var removeableValue in removeableValues)
                            model.DisturbanceValueEntities.Remove(removeableValue);

                        if (removeableValues.Count > 0)
                            model.DisturbanceValueEntities.Add(new DisturbanceValueDataModel { Description = "( ausgefilterte Kategorien )" });
                    }
                }

                foreach (var removeable in removables)
                    models.Remove(removeable);
            }
        }

        #endregion
    }
}