 class AlignAdjacentWalls : IExternalCommand
    {
        private Wall wall0;
        private Wall wall1;  
        private Curve newCurve;
        private ElementId E1Id;
        private ElementId E0Id;

        const double smallExp = 1.0e-9;


        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            //Get application and document objects
            UIApplication uiApp = commandData.Application;
            UIDocument uiDoc = uiApp.ActiveUIDocument;
            Autodesk.Revit.ApplicationServices.Application app = uiApp.Application;
            Autodesk.Revit.DB.Document doc = uiDoc.Document;



            try
            {
                //--------------------------
                //calls to methods
                //--------------------------

                int counter = 0;
                bool boolContinue = true;

                //Warning
                TaskDialog.Show("Warning - You are Beta Testing", "Synchronize your work before using the add-in!" 
                + "\n" + "Pick the structural wall, then the lining wall");

                while (boolContinue == true)
                {
                    //Select the walls
                    ISelectionFilter tagSelFilter = new WallSelectionFilter(doc);
                    Reference refFace = uiDoc.Selection.PickObject(ObjectType.Element, tagSelFilter, "Select A wall to align To, then the wall to move to that alignment");

                    //Regenerate the view
                    doc.Regenerate();

                    //Get the element from the selected wall
                    Element element = doc.GetElement(refFace.ElementId);

                    //trigger to exit loop, user selects a door
                    if (element.Category.Name == "Doors" || element.Category.Name == "Windows")
                    {
                        boolContinue = false;
                        break;
                    }

                    //Get the Element Id of the wall
                    ElementId Wallid = element.Id;


                    //Cast the element to a wall
                    Wall wall = element as Wall;


                    //set the 1st wall to Wall0
                    if (counter % 2 == 0)
                    {
                        E0Id = Wallid;
                        wall0 = wall;
                    }

                    //set the 2nd wall to Wall1
                    if (counter % 2 == 1)
                    {
                        E1Id = Wallid;
                        wall1 = wall;


                        if (IsParallel(doc, E0Id, E1Id) == false)
                        {
                            TaskDialog.Show("Walls are not Parallel!", "Result = " + IsParallel(doc, E0Id, E1Id) + "\n" + "Select two adjacent parallel walls");
                            boolContinue = false;
                            break;
                        }

                    }









                    //Call to method to align/ the alignment lock
                    if (counter % 2 == 1)
                    {

                        //Cancel operation if user selects the same wall and if the walls are not parallel
                        if (E0Id != E1Id)
                        {
                            //Call to main code
                            //Variables
                            PlanarFace f0 = null;
                            XYZ translate = null;



                            //Check if wall orientation is incorrect and flip wall1 so that flip arrows face into the room
                            FlipWallIfOrientBackwards(doc, E0Id, E1Id);
                            

                            //distance
                            double di = distMidPTtoPlanarFace(doc, E1Id, E0Id, "Interior");
                            //distance
                            double de = distMidPTtoPlanarFace(doc, E1Id, E0Id, "Exterior");



                            //Calculate the shorter path between the wall1 midpoint and the wall0 int or ext planarface and use as the translation XYZ(0,1 or fatal error)
                            if (di < de)
                            {
                                translate = getIntOrExtCurve(doc, E0Id, "Interior").GetEndPoint(0) - getIntOrExtCurve(doc, E1Id, "Interior").GetEndPoint(1);
                                //Create References for alignment locking walls
                                f0 = getFaceFromSolidGeom(doc, app, E0Id, "Interior");
                            }

                            if (de < di)
                            {
                                translate = getIntOrExtCurve(doc, E0Id, "Exterior").GetEndPoint(0) - getIntOrExtCurve(doc, E1Id, "Interior").GetEndPoint(1);
                                //Create References for alignment locking walls
                                f0 = getFaceFromSolidGeom(doc, app, E0Id, "Exterior");
                            }



                            //Move walls to align along finish face interior location line
                            ElementTransformUtils.MoveElement(doc, E1Id, translate);




                            //Create Alignment lock
                            CreateDimensionElement(doc, f0, getFaceFromSolidGeom(doc, app, E1Id, "Interior"));



                            //Set Wall Location Lines
                            setWallLocationLine(doc, E0Id);
                            setWallLocationLine(doc, E1Id);


                            //Join The two walls geometry
                            joinWallGeometry(doc, E0Id, E1Id);


                            //Set the joins to mitre to remove abutting join line
                            setWallEndJoinType(doc, E0Id);
                            setWallEndJoinType(doc, E1Id);

                        }
                        else 
                        {
                            TaskDialog.Show("Walls are not unique!", "Select two unique adjacent walls" + "\n" + 
                                "Wall ID 0:" + E0Id.IntegerValue.ToString() + "\n" +
                                "Wall ID 1:" + E1Id.IntegerValue.ToString());
                            boolContinue = false;
                            break; 
                        }
                    }







                    //counter after selections
                    counter++;
                }




                //--------------------------
                //--------------------------
                return Result.Succeeded;
            }//end execute method

            //If the user right-clicks or presses Esc, handle the exception
            catch (Autodesk.Revit.Exceptions.OperationCanceledException)
            {
                return Result.Cancelled;
            }
            //Catch other errors
            catch (Exception ex)
            {
                message = ex.Message;
                return Result.Failed;
            }


        }//end exec










        //Use ElementId where possible
        private static Wall getWall(Document doc, ElementId EId)
        {
            Element e = doc.GetElement(EId);
            Wall wall = e as Wall;

            return wall;
        }









        //Flip wall if interior pt is further than exterior pt from base interior pt
        private static void FlipWallIfOrientBackwards(Document doc, ElementId E0Id, ElementId E1Id)
        {
            try
            {
                

                //Interior Face 
                double di = distMidPTtoPlanarFace(doc, E0Id, E1Id, "Interior");

                //Exterior Face
                double de = distMidPTtoPlanarFace(doc, E0Id, E1Id, "Exterior");


                //The greater length between points will flip the wall
                if (di > de)
                {
                    getWall(doc, E1Id).Flip();
                }

            }
            catch (Exception ex)
            {
                TaskDialog.Show("Other Error", ex.Message);
            }
        }








        //Calculate the distance from a midpoint to a planarFace, Note: switching the argument order switches which wall is wall0 and wall1
        private static double distMidPTtoPlanarFace(Document doc, ElementId E0Id, ElementId E1Id, String faceSide)
        {
            try
            {
                

            //Interior Face 
            PlanarFace pf = getIntOrExtPlanarFace(doc, E1Id, faceSide);
            XYZ vector = wallMidpoint(doc, E0Id) - pf.Origin;
            //Distance from point to face
            double distance = vector.DotProduct(-pf.Normal);

            return distance;

            }

            catch (Exception ex)
            {
                TaskDialog.Show("Other Error", ex.Message);
                return 0;
            }
        }








        //Returns the planar face of either the exterior or interior of the wall
        private static PlanarFace getIntOrExtPlanarFace(Document doc, ElementId EId, String FaceSide)
        {
            try
            {

                IList<Reference> sideFaces = HostObjectUtils.GetSideFaces(getWall(doc, EId), ShellLayerType.Interior);

                if (FaceSide == "Interior")
                {
                    sideFaces = HostObjectUtils.GetSideFaces(getWall(doc, EId), ShellLayerType.Interior);
                }
                if (FaceSide == "Exterior")
                {
                    sideFaces = HostObjectUtils.GetSideFaces(getWall(doc, EId), ShellLayerType.Exterior);
                }


                Element e2 = doc.GetElement(sideFaces[0]);
                Face face = e2.GetGeometryObjectFromReference(sideFaces[0]) as Face;

                PlanarFace planarface = (PlanarFace)face;

                return planarface;

            }

            catch (Exception ex)
            {
                TaskDialog.Show("Other Error", ex.Message);
                return null;
            }

        }//end method








        //Provide references to the walls
        private PlanarFace getFaceFromSolidGeom(Document doc, Application app, ElementId EId, String faceSide)
        {
            try
            {

                PlanarFace pf = null;

                Options opt = app.Create.NewGeometryOptions();
                opt.ComputeReferences = true;


                GeometryElement geo = getWall(doc, EId).get_Geometry(opt);
                foreach (GeometryObject obj in geo)
                {
                    Solid solid = obj as Solid;
                    if (solid != null)
                    {
                        FaceArray fa = solid.Faces;
                        foreach (Face f in fa)
                        {
                            pf = f as PlanarFace;

                            if (pf != null)
                            {


                                if (pf.Intersect(getIntOrExtCurve(doc, EId, faceSide)) == SetComparisonResult.Subset)//Subset
                                {
                                    //Return the face that intersects the curve of the interior/exterior face
                                    return pf;
                                }



                            }
                        }
                    }
                }

                return null;

            }
            catch (Exception ex)
            {
                TaskDialog.Show("Other Error", ex.Message);
                return null;
            }
        }








        //Get Interior curves for translation or movement of wall1 to align with wall0
        private Curve getIntOrExtCurve(Document doc, ElementId EId, String FaceSide)
        {
            try
            {

                IList<Reference> sideFaces = HostObjectUtils.GetSideFaces(getWall(doc, EId), ShellLayerType.Interior);

                    if (FaceSide == "Interior")
                    {
                        sideFaces = HostObjectUtils.GetSideFaces(getWall(doc, EId), ShellLayerType.Interior);
                    }
                    if (FaceSide == "Exterior")
                    {
                        sideFaces = HostObjectUtils.GetSideFaces(getWall(doc, EId), ShellLayerType.Exterior);
                    }

                Element e2 = doc.GetElement(sideFaces[0]);
                Face face = e2.GetGeometryObjectFromReference(sideFaces[0]) as Face;
                // Get edge loops as curve loops.
                IList<CurveLoop> curveLoops = face.GetEdgesAsCurveLoops();

                foreach (CurveLoop curveloop in curveLoops)
                {
                    foreach (Curve curveitems in curveloop)
                    {
                        int EdgeCounter = 0;

                        //get the wall approx length
                        LocationCurve lc = getWall(doc, EId).Location as LocationCurve;

                        //Set the Curve line
                        newCurve = curveitems;


                        EdgeCounter++;
                    }

                }
                //Return a curve
                return newCurve;

            }
            catch (Exception ex)
            {
                TaskDialog.Show("Other Error", ex.Message);
                return null;
            }

        }//end method









        //Create an alignment lock between wall faces
        private static Dimension CreateDimensionElement(Document doc, Face r1, Face r2)
        {
            try
            {
                View view = doc.ActiveView;
                Dimension dim = doc.Create.NewAlignment(view, r1.Reference, r2.Reference);

                return dim;

            }
            catch (Exception ex)
            {
                TaskDialog.Show("Other Error", ex.Message);
                return null;
            }
        }



        





        //Return the midpoint of a location curves endpoints
        private static XYZ wallMidpoint(Document doc, ElementId EId)
        {
            try
            {

            //Line from Wall element
                LocationCurve locationcurve = getWall(doc, EId).Location as LocationCurve;
            Line line = locationcurve.Curve as Line;
            
            //Endpoints from Line
            XYZ p = line.GetEndPoint(0);
            XYZ q = line.GetEndPoint(1);

            return p + 0.5 * (q - p);

             }
            catch (Exception ex)
            {
                TaskDialog.Show("Other Error", ex.Message);
                return null;
            }
        }








        //Check if Wall0 is parallel with Wall1
        private static bool IsParallel(Document doc, ElementId E0Id, ElementId E1Id)
        {

            

            try
            {
            //get normals from planar faces of wall0 and wall1
                XYZ normWall0 = getIntOrExtPlanarFace(doc, E0Id, "Interior").Normal;
                XYZ normWall1 = getIntOrExtPlanarFace(doc, E1Id, "Interior").Normal;

            double angle = normWall0.AngleTo(normWall1);

            //Returns true if angle is very close to smallExp or if the angle minus PI = 180 is cose to smallExp
            return smallExp > angle ||  Math.Abs(angle - Math.PI) < smallExp;

            }
            catch (Exception ex)
            {
                TaskDialog.Show("Other Error", ex.Message);
                return false;
            }
        }









        //Wall join geometry/ end joins/ location line/ disallow joins
        //Wall Properties, needs review
        private static void setWallLocationLine(Document doc, ElementId EId)
        {
            getWall(doc, EId).get_Parameter(BuiltInParameter.WALL_KEY_REF_PARAM).Set(3);
        }

        private static void setWallEndJoinType(Document doc, ElementId EId)
        {
            //Line from Wall element
            LocationCurve locationcurve = getWall(doc, EId).Location as LocationCurve;
            //Set the join type to none
            locationcurve.set_JoinType(0, JoinType.Miter);
            locationcurve.set_JoinType(1, JoinType.Miter);
        }

        private static void joinWallGeometry(Document doc, ElementId E0Id, ElementId E1Id)
        {
            try
            {

                if (JoinGeometryUtils.AreElementsJoined(doc, getWall(doc, E0Id), getWall(doc, E1Id)) == false)
                {
                    JoinGeometryUtils.JoinGeometry(doc, getWall(doc, E0Id), getWall(doc, E1Id));
                }
                else
                {
                    TaskDialog.Show("Walls are already joined!", "Walls join attempt invocation failure:" + "\n" +
                    "Wall ID 0:" + getWall(doc, E0Id).Id.IntegerValue.ToString() + "\n" +
                    "Wall ID 1:" + getWall(doc, E1Id).Id.IntegerValue.ToString());
                }

            }
            catch (Exception ex)
            {
                TaskDialog.Show("Other Error", ex.Message);

            }
        }

        // private void disallowWallJoinsAtEnds(Wall wall, int intEnd)
        // {
        //     WallUtils.DisallowWallJoinAtEnd(wall, intEnd);
        // }






    }//end class







    //Wall selection filter
    public class WallSelectionFilter : ISelectionFilter
    {
        Document doc = null;


            public WallSelectionFilter(Document document)
            {
                doc = document;
            }


            public bool AllowElement(Element element)
            {
                //user cant select any one of these
                if (element.Category.Id.IntegerValue == (int)BuiltInCategory.OST_Walls) return true;
                if (element.Category.Id.IntegerValue == (int)BuiltInCategory.OST_Doors) return true;
                if (element.Category.Id.IntegerValue == (int)BuiltInCategory.OST_Windows) return true;
                return false;
            }



            public bool AllowReference(Reference refer, XYZ pos)
            {
                return true; // Only return true for planar faces. Non-planar faces will not be selectable
            }



    }