using System.ComponentModel;
using FastReport.Utils;
using FastReport.Data;
using System.Drawing.Design;

namespace FastReport
{
  /// <summary>
  /// Specifies a sort order.
  /// </summary>
  /// <remarks>
  /// This enumeration is used in the group header and in the "Matrix" object.
  /// </remarks>
  public enum SortOrder 
  { 
    /// <summary>
    /// Specifies no sort (natural order).
    /// </summary>
    None, 
    
    /// <summary>
    /// Specifies an ascending sort order.
    /// </summary>
    Ascending,

    /// <summary>
    /// Specifies a descending sort order.
    /// </summary>
    Descending
  }

  /// <summary>
  /// Represents a group header band.
  /// </summary>
  /// <remarks>
  /// A simple group consists of one <b>GroupHeaderBand</b> and the <b>DataBand</b> that is set 
  /// to the <see cref="Data"/> property. To create the nested groups, use the <see cref="NestedGroup"/> property.
  /// <note type="caution">
  /// Only the last nested group can have data band.
  /// </note>
  /// <para/>Use the <see cref="Condition"/> property to set the group condition. The <see cref="SortOrder"/>
  /// property can be used to set the sort order for group's data rows. You can also use the <b>Sort</b>
  /// property of the group's <b>DataBand</b> to specify additional sort.
  /// </remarks>
  /// <example>This example shows how to create nested groups.
  /// <code>
  /// ReportPage page = report.Pages[0] as ReportPage;
  /// 
  /// // create the main group
  /// GroupHeaderBand mainGroup = new GroupHeaderBand();
  /// mainGroup.Height = Units.Millimeters * 10;
  /// mainGroup.Name = "MainGroup";
  /// mainGroup.Condition = "[Orders.CustomerName]";
  /// // add a group to the page
  /// page.Bands.Add(mainGroup);
  /// 
  /// // create the nested group
  /// GroupHeaderBand nestedGroup = new GroupHeaderBand();
  /// nestedGroup.Height = Units.Millimeters * 10;
  /// nestedGroup.Name = "NestedGroup";
  /// nestedGroup.Condition = "[Orders.OrderDate]";
  /// // add it to the main group
  /// mainGroup.NestedGroup = nestedGroup;
  /// 
  /// // create a data band
  /// DataBand dataBand = new DataBand();
  /// dataBand.Height = Units.Millimeters * 10;
  /// dataBand.Name = "GroupData";
  /// dataBand.DataSource = report.GetDataSource("Orders");
  /// // connect the databand to the nested group
  /// nestedGroup.Data = dataBand;
  /// </code>
  /// </example>
  public partial class GroupHeaderBand : HeaderFooterBandBase
  {
    #region Fields
    private GroupHeaderBand nestedGroup;
    private DataBand data;
    private GroupFooterBand groupFooter;
    private DataHeaderBand header;
    private DataFooterBand footer;
    private string condition;
    private SortOrder sortOrder;
    private bool keepTogether;
    private bool resetPageNumber;
    private object groupValue;
    #endregion

    #region Properties
    /// <summary>
    /// Gets or sets a nested group.
    /// </summary>
    /// <remarks>
    /// Use this property to create nested groups.
    /// <note type="caution">
    /// Only the last nested group can have data band.
    /// </note>
    /// </remarks>
    /// <example>
    /// This example demonstrates how to create a group with nested group.
    /// <code>
    /// ReportPage page;
    /// GroupHeaderBand group = new GroupHeaderBand();
    /// group.NestedGroup = new GroupHeaderBand();
    /// group.NestedGroup.Data = new DataBand();
    /// page.Bands.Add(group);
    /// </code>
    /// </example>
    [Browsable(false)]
    public GroupHeaderBand NestedGroup
    {
      get { return nestedGroup; }
      set 
      {
        SetProp(nestedGroup, value);
        nestedGroup = value;
      }
    }

    /// <summary>
    /// Gets or sets the group data band.
    /// </summary>
    /// <remarks>
    /// Use this property to add a data band to a group. Note: only the last nested group can have Data band.
    /// </remarks>
    /// <example>
    /// This example demonstrates how to add a data band to a group.
    /// <code>
    /// ReportPage page;
    /// GroupHeaderBand group = new GroupHeaderBand();
    /// group.Data = new DataBand();
    /// page.Bands.Add(group);
    /// </code>
    /// </example>
    [Browsable(false)]
    public DataBand Data
    {
      get { return data; }
      set
      {
        SetProp(data, value);
        data = value;
      }
    }

    /// <summary>
    /// Gets or sets a group footer.
    /// </summary>
    [Browsable(false)]
    public GroupFooterBand GroupFooter
    {
      get { return groupFooter; }
      set
      {
        SetProp(groupFooter, value);
        groupFooter = value;
      }
    }

    /// <summary>
    /// Gets or sets a header band.
    /// </summary>
    [Browsable(false)]
    public DataHeaderBand Header
    {
      get { return header; }
      set
      {
        SetProp(header, value);
        header = value;
      }
    }

    /// <summary>
    /// Gets or sets a footer band.
    /// </summary>
    /// <remarks>
    /// To access a group footer band, use the <see cref="GroupFooter"/> property.
    /// </remarks>
    [Browsable(false)]
    public DataFooterBand Footer
    {
      get { return footer; }
      set
      {
        SetProp(footer, value);
        footer = value;
      }
    }

    /// <summary>
    /// Gets or sets the group condition.
    /// </summary>
    /// <remarks>
    /// This property can contain any valid expression. When running a report, this expression is calculated 
    /// for each data row. When the value of this condition is changed, FastReport starts a new group.
    /// </remarks>
    [Category("Data")]
    [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))]
    public string Condition
    {
      get { return condition; }
      set { condition = value; }
    }
    
    /// <summary>
    /// Gets or sets the sort order.
    /// </summary>
    /// <remarks>
    /// FastReport can sort data rows automatically using the <see cref="Condition"/> value.
    /// </remarks>
    [DefaultValue(SortOrder.Ascending)]
    [Category("Behavior")]
    public SortOrder SortOrder
    {
      get { return sortOrder; }
      set { sortOrder = value; }
    }

    /// <summary>
    /// Gets or sets a value indicating that the group should be printed together on one page.
    /// </summary>
    [DefaultValue(false)]
    [Category("Behavior")]
    public bool KeepTogether
    {
      get { return keepTogether; }
      set { keepTogether = value; }
    }

    /// <summary>
    /// Gets or sets a value that determines whether to reset the page numbers when this group starts print.
    /// </summary>
    /// <remarks>
    /// Typically you should set the <see cref="BandBase.StartNewPage"/> property to <b>true</b> as well.
    /// </remarks>
    [DefaultValue(false)]
    [Category("Behavior")]
    public bool ResetPageNumber
    {
      get { return resetPageNumber; }
      set { resetPageNumber = value; }
    }

    internal DataSourceBase DataSource
    {
      get 
      { 
        DataBand dataBand = GroupDataBand;
        return dataBand == null ? null : dataBand.DataSource;
      }  
    }
    
    internal DataBand GroupDataBand
    {
      get
      {
        GroupHeaderBand group = this;
        while (group != null)
        {
          if (group.Data != null)
            return group.Data;
          group = group.NestedGroup;
        }
        return null;
      }  
    }
    #endregion

    #region IParent
    /// <inheritdoc/>
    public override void GetChildObjects(ObjectCollection list)
    {
      base.GetChildObjects(list);
      if (!IsRunning)
      {
        list.Add(header);
        list.Add(nestedGroup);
        list.Add(data);
        list.Add(groupFooter);
        list.Add(footer);
      }
    }

    /// <inheritdoc/>
    public override bool CanContain(Base child)
    {
      return base.CanContain(child) || 
        (child is DataBand && nestedGroup == null && data == null) || 
        (child is GroupHeaderBand && (nestedGroup == null || nestedGroup is GroupHeaderBand) && data == null) || 
        child is GroupFooterBand || child is DataHeaderBand || child is DataFooterBand;
    }

    /// <inheritdoc/>
    public override void AddChild(Base child)
    {
      if (IsRunning)
      {
        base.AddChild(child);
        return;
      }
      
      if (child is GroupHeaderBand)
        NestedGroup = child as GroupHeaderBand;
      else if (child is DataBand)
        Data = child as DataBand;
      else if (child is GroupFooterBand)
        GroupFooter = child as GroupFooterBand;
      else if (child is DataHeaderBand)
        Header = child as DataHeaderBand;
      else if (child is DataFooterBand)
        Footer = child as DataFooterBand;
      else
        base.AddChild(child);
    }

    /// <inheritdoc/>
    public override void RemoveChild(Base child)
    {
      base.RemoveChild(child);
      if (IsRunning)
        return;

      if (child is GroupHeaderBand && nestedGroup == child)
        NestedGroup = null;
      if (child is DataBand && data == child as DataBand)
        Data = null;
      if (child is GroupFooterBand && groupFooter == child)
        GroupFooter = null;
      if (child is DataHeaderBand && header == child)
        Header = null;
      if (child is DataFooterBand && footer == child)
        Footer = null;
    }
    #endregion

    #region Public Methods
    /// <inheritdoc/>
    public override void Assign(Base source)
    {
      base.Assign(source);
      
      GroupHeaderBand src = source as GroupHeaderBand;
      Condition = src.Condition;
      SortOrder = src.SortOrder;
      KeepTogether = src.KeepTogether;
      ResetPageNumber = src.ResetPageNumber;
    }

    /// <inheritdoc/>
    public override void Serialize(FRWriter writer)
    {
      GroupHeaderBand c = writer.DiffObject as GroupHeaderBand;
      base.Serialize(writer);
      if (writer.SerializeTo == SerializeTo.Preview)
        return;
      
      if (Condition != c.Condition)
        writer.WriteStr("Condition", Condition);
      if (SortOrder != c.SortOrder)
        writer.WriteValue("SortOrder", SortOrder);
      if (KeepTogether != c.KeepTogether)
        writer.WriteBool("KeepTogether", KeepTogether);
      if (ResetPageNumber != c.ResetPageNumber)
        writer.WriteBool("ResetPageNumber", ResetPageNumber);
    }

    /// <inheritdoc/>
    public override string[] GetExpressions()
    {
      return new string[] { Condition };
    }

    internal override bool IsEmpty()
    {
      if (NestedGroup != null)
        return NestedGroup.IsEmpty();
      else if (Data != null)
        return Data.IsEmpty();
      return base.IsEmpty();
    }
    
    internal void InitDataSource()
    {
      DataBand dataBand = GroupDataBand;
      GroupHeaderBand group = this;
      int index = 0;
      // insert group sort to the databand
      while (group != null)
      {
        if (group.SortOrder != SortOrder.None)
        {
          dataBand.Sort.Insert(index, new Sort(group.Condition, group.SortOrder == SortOrder.Descending));
          index++;
        }
        group = group.NestedGroup;
      }
      
      dataBand.InitDataSource();
    }
    
    internal void FinalizeDataSource()
    {
      DataBand dataBand = GroupDataBand;
      GroupHeaderBand group = this;
      // remove group sort from the databand
      while (group != null)
      {
        if (group.SortOrder != SortOrder.None)
          dataBand.Sort.RemoveAt(0);
        group = group.NestedGroup;
      }
    }
    
    internal void ResetGroupValue()
    {
      if (!string.IsNullOrEmpty(Condition))
      {
        groupValue = Report.Calc(Condition);
      }
      else
      {
        throw new GroupHeaderHasNoGroupCondition(Name);
      }
    }

    internal bool GroupValueChanged()
    {
      object value = null;
      if (!string.IsNullOrEmpty(Condition))
      { 
        value = Report.Calc(Condition);
      }
      else
      {
        throw new GroupHeaderHasNoGroupCondition(Name);
      }
      if (groupValue == null)
      {
        if (value == null)
          return false;
        return true;  
      }
      return !groupValue.Equals(value);
    }
    #endregion
    
    /// <summary>
    /// Initializes a new instance of the <see cref="GroupHeaderBand"/> class with default settings.
    /// </summary>
    public GroupHeaderBand()
    {
      condition = "";
      sortOrder = SortOrder.Ascending;
    }
  }
}