// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

namespace System.Data.Entity.Core.Mapping.ViewGeneration.Structures
{
    using System.Collections.Generic;
    using System.Data.Entity.Core.Common.Utils;
    using System.Data.Entity.Core.Mapping.ViewGeneration.Validation;
    using System.Data.Entity.Core.Metadata.Edm;
    using System.Data.Entity.Utilities;
    using System.Diagnostics;
    using System.Text;

    // <summary>
    // This class contains a pair of cell queries which is essentially a
    // constraint that they are equal. A cell is initialized with a C or an
    // S Query which it exposes as properties but it also has the notion of
    // "Left" and "Right" queries -- left refers to the side for which a
    // view is being generated
    // For example, to
    // specify a mapping for CPerson to an SPerson table, we have
    // [(p type Person) in P : SPerson]
    // (p.pid, pid)
    // (p.name, name)
    // This really denotes the equality of two queries:
    // (C) SELECT (p type Person) AS D1, p.pid, p.name FROM p in P WHERE D1
    // (S) SELECT True AS D1, pid, name FROM SPerson WHERE D1
    // For more details, see the design doc
    // </summary>
    internal class Cell : InternalBase
    {
        // effects: Creates a cell with the C and S queries 
        private Cell(CellQuery cQuery, CellQuery sQuery, CellLabel label, int cellNumber)
        {
            DebugCheck.NotNull(label);
            m_cQuery = cQuery;
            m_sQuery = sQuery;
            m_label = label;
            m_cellNumber = cellNumber;
            Debug.Assert(
                m_sQuery.NumProjectedSlots == m_cQuery.NumProjectedSlots,
                "Cell queries disagree on the number of projected fields");
        }

        // <summary>
        // Copy Constructor
        // </summary>
        internal Cell(Cell source)
        {
            m_cQuery = new CellQuery(source.m_cQuery);
            m_sQuery = new CellQuery(source.m_sQuery);
            m_label = new CellLabel(source.m_label);
            m_cellNumber = source.m_cellNumber;
        }

        private readonly CellQuery m_cQuery;
        private readonly CellQuery m_sQuery;
        private readonly int m_cellNumber; // cell number that identifies this cell
        private readonly CellLabel m_label; // The File and Path Info for the CSMappingFragment
        // that the Cell was constructed over.
        // The view cell relation for all projected slots in this
        private ViewCellRelation m_viewCellRelation;

        // effects: Returns the C query
        internal CellQuery CQuery
        {
            get { return m_cQuery; }
        }

        // effects: Returns the S query
        internal CellQuery SQuery
        {
            get { return m_sQuery; }
        }

        // effects: Returns the CSMappingFragment (if any)
        // that the Cell was constructed over.
        internal CellLabel CellLabel
        {
            get { return m_label; }
        }

        // effects: Returns the cell label (if any)
        internal int CellNumber
        {
            get { return m_cellNumber; }
        }

        internal string CellNumberAsString
        {
            get { return StringUtil.FormatInvariant("V{0}", CellNumber); }
        }

        // effects: Determines all the identifiers used in this and adds them to identifiers
        internal void GetIdentifiers(CqlIdentifiers identifiers)
        {
            m_cQuery.GetIdentifiers(identifiers);
            m_sQuery.GetIdentifiers(identifiers);
        }

        // effects: Given a cell, determines the paths to which the paths in
        // columns map to in the C-space and returns them. If some columns
        // are not projected in the cell, or if the corresponding properties
        // are not mapped into C-space, returns null
        internal Set<EdmProperty> GetCSlotsForTableColumns(IEnumerable<MemberPath> columns)
        {
            var fieldNums = SQuery.GetProjectedPositions(columns);
            if (fieldNums == null)
            {
                return null;
            }

            // The fields are mapped -- see if they are mapped on the
            // cSide and they correspond to the primary key of the
            // entity set

            var cSideMembers = new Set<EdmProperty>();
            foreach (var fieldNum in fieldNums)
            {
                var projectedSlot = CQuery.ProjectedSlotAt(fieldNum);
                var slot = projectedSlot as MemberProjectedSlot;
                if (slot != null)
                {
                    // We can call LastMember since columns do not map to
                    // extents or memberEnds. Can cast to EdmProperty since it
                    // cannot be an association end
                    cSideMembers.Add((EdmProperty)slot.MemberPath.LeafEdmMember);
                }
                else
                {
                    return null;
                }
            }
            return cSideMembers;
        }

        // effects: Returns the C query for ViewTarget.QueryView and S query for ViewTarget.UpdateView
        internal CellQuery GetLeftQuery(ViewTarget side)
        {
            return side == ViewTarget.QueryView ? m_cQuery : m_sQuery;
        }

        // effects: Returns the S query for ViewTarget.QueryView and C query for ViewTarget.UpdateView
        internal CellQuery GetRightQuery(ViewTarget side)
        {
            return side == ViewTarget.QueryView ? m_sQuery : m_cQuery;
        }

        // effects: Returns the relation that contains all the slots being
        // projected in this cell 
        internal ViewCellRelation CreateViewCellRelation(int cellNumber)
        {
            if (m_viewCellRelation != null)
            {
                return m_viewCellRelation;
            }
            GenerateCellRelations(cellNumber);
            return m_viewCellRelation;
        }

        private void GenerateCellRelations(int cellNumber)
        {
            // Generate the view cell relation
            var projectedSlots = new List<ViewCellSlot>();
            // construct a ViewCellSlot for each slot
            Debug.Assert(
                CQuery.NumProjectedSlots == SQuery.NumProjectedSlots,
                "Cell queries in cell have a different number of slots");
            for (var i = 0; i < CQuery.NumProjectedSlots; i++)
            {
                var cSlot = CQuery.ProjectedSlotAt(i);
                var sSlot = SQuery.ProjectedSlotAt(i);
                Debug.Assert(cSlot != null, "Has cell query been normalized?");
                Debug.Assert(sSlot != null, "Has cell query been normalized?");

                var cJoinSlot = (MemberProjectedSlot)cSlot;
                var sJoinSlot = (MemberProjectedSlot)sSlot;

                var slot = new ViewCellSlot(i, cJoinSlot, sJoinSlot);
                projectedSlots.Add(slot);
            }
            m_viewCellRelation = new ViewCellRelation(this, projectedSlots, cellNumber);
        }

        internal override void ToCompactString(StringBuilder builder)
        {
            CQuery.ToCompactString(builder);
            builder.Append(" = ");
            SQuery.ToCompactString(builder);
        }

        internal override void ToFullString(StringBuilder builder)
        {
            CQuery.ToFullString(builder);
            builder.Append(" = ");
            SQuery.ToFullString(builder);
        }

        public override string ToString()
        {
            return ToFullString();
        }

        // effects: Prints the cells in some human-readable form
        internal static void CellsToBuilder(StringBuilder builder, IEnumerable<Cell> cells)
        {
            // Print mapping
            builder.AppendLine();
            builder.AppendLine("=========================================================================");
            foreach (var cell in cells)
            {
                builder.AppendLine();
                StringUtil.FormatStringBuilder(builder, "Mapping Cell V{0}:", cell.CellNumber);
                builder.AppendLine();

                builder.Append("C: ");
                cell.CQuery.ToFullString(builder);
                builder.AppendLine();
                builder.AppendLine();

                builder.Append("S: ");
                cell.SQuery.ToFullString(builder);
                builder.AppendLine();
            }
        }

        internal static Cell CreateCS(CellQuery cQuery, CellQuery sQuery, CellLabel label, int cellNumber)
        {
            return new Cell(cQuery, sQuery, label, cellNumber);
        }
    }
}
