using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Autodesk.Revit;
using Autodesk.Revit.Elements;
using Autodesk.Revit.Geometry;
using Autodesk.Revit.Symbols;
using System.Windows.Forms;
using RvtElement = Autodesk.Revit.Element;
using GeoElement = Autodesk.Revit.Geometry.Element;

namespace CH_Revit
{
  public class WallProfiles
  {
    const double PRECISION = 0.00001;

    ArrayList m_sidingTypes = new ArrayList();
    public ArrayList sidingTypes
    {
      get
      {
        return m_sidingTypes;
      }
    }

    Object m_selectedSidingType;
    public Object SelectedSidingType
    {
      get
      {
        return m_selectedSidingType;
      }
      set
      {
        m_selectedSidingType = value as WallType;
      }
    }

    Autodesk.Revit.Application app;
    public WallProfiles( Autodesk.Revit.Application _app )
    {
      app = _app;
    }

    public Dictionary<XYZ, List<List<XYZ>>> m_Profiles = new Dictionary<XYZ, List<List<XYZ>>>();
    public Level m_Level = new Level();

    public bool Processing()
    {
      Document doc = app.ActiveDocument;

      foreach( WallType wallType in doc.WallTypes )
      {
        if( wallType == null )
          continue;
        if( !wallType.Name.Contains( "Siding" ) )
          continue;

        m_sidingTypes.Add( wallType );
      }

      using( WallSidingForm dlg = new WallSidingForm( this ) )
      {
        if( DialogResult.OK != dlg.ShowDialog() )
          return false;
      }

      double offset = 0.5 * ( m_selectedSidingType as WallType ).Width; // for siding center line

      List<RvtElement> walls = new List<RvtElement>();
      if( !Util.GetSelectedElementsOrAll( walls, doc, typeof( Wall ) ) )
      {
        Selection sel = doc.Selection;
        Util.InfoMsg( ( sel.Elements.Size > 0 ) ? "please select some wall elements." : "no wall elements found." );
        return false;
      }

      m_Level = walls[0].Level; // assume all selected walls at same level

      XYZ p, q, v, w;
      Options opt = app.Create.NewGeometryOptions();
      foreach( Wall wall in walls )
      {
        List<List<XYZ>> polygons = new List<List<XYZ>>();

        string desc = Util.ElementDescription( wall );

        LocationCurve curve = wall.Location as LocationCurve;
        if( curve == null )
        {
          Util.InfoMsg( desc + ": no wall curve found." );
          return false;
        }

        p = curve.Curve.get_EndPoint( 0 );
        q = curve.Curve.get_EndPoint( 1 );
        v = q - p;
        v = v.Normalized;
        w = XYZ.BasisZ.Cross( v ).Normalized;
        if( wall.Flipped )
          w = -w;

        GeoElement geo = wall.get_Geometry( opt );
        GeometryObjectArray objects = geo.Objects;
        foreach( GeometryObject obj in objects )
        {
          Solid solid = obj as Solid;
          if( solid != null )
            GetProfile( polygons, solid, v, w, offset );
        }

        m_Profiles.Add( wall.Orientation.Normalized, polygons );

        Creator creator = new Creator( app );
        creator.DrawPolygons( polygons );
      }

      int n = m_Profiles.Count;
      Debug.WriteLine( string.Format( "{0} boundary loop{1} found.", n, Util.PluralSuffix( n ) ) );

      return true;
    }

    bool GetProfile(
      List<List<XYZ>> polygons, // wall profile w/ openning(s)
      Solid solid,
      XYZ v, // vector towards exterior wall face
      XYZ w, // vector along wall centre line
      double off ) // for wall center line
    {
      double d, dmax = 0.0;
      PlanarFace outermost = null;

      FaceArray faces = solid.Faces;
      foreach( Face f in faces )
      {
        PlanarFace pf = f as PlanarFace;
        if( pf != null && Util.IsVertical( pf ) && Util.IsZero( v.Dot( pf.Normal ) ) )
        {
          d = pf.Origin.Dot( w );
          if( ( outermost == null ) || ( dmax < d ) )
          {
            outermost = pf;
            dmax = d;
          }
        }
      }

      if( outermost != null )
      {
        XYZ voffset = off * w;
        XYZ p, q = XYZ.Zero;
        bool first;
        int i, n;

        EdgeArrayArray loops = outermost.EdgeLoops;

        XYZ v1 = new XYZ(); // outer loop vector
        XYZ v2 = new XYZ(); // inner loop(s) vector

        int i1 = 1; // 1 for outer loop
        foreach( EdgeArray loop in loops )
        {
          List<XYZ> vertices = new List<XYZ>();
          first = true;

          int i2 = 1; // 1 for 1st edge
          foreach( Edge e in loop )
          {
            XYZArray points = e.Tessellate();
            p = points.get_Item( 0 );

            if( !first )
            {
              Debug.Assert(
                p.AlmostEqual( q ),
                "expected subsequent start point" +
                " to equal previous end point" );
            }

            if( i1 == 1 && i2 == 1 ) // 1st edge of outer loop
              v1 = points.get_Item( 1 ).Subtract( p );
            if( i1 > 1 && i2 == 1 ) // 1st edge of inner loop(s)
              v2 = points.get_Item( 1 ).Subtract( p );

            n = points.Size;
            q = points.get_Item( n - 1 );
            for( i = 0; i < n - 1; ++i )
            {
              XYZ a = points.get_Item( i );
              a += voffset;
              vertices.Add( a );
            }

            i2++;
          }

          q += voffset;

          Debug.Assert(
            q.AlmostEqual( vertices[0] ),
            "expected last end point to equal"
            + " first start point" );

          /* w/o the following codes, NewWall may can not be created, don't know exactly why?
          //MessageBox.Show((v1.Angle(v2) * 180 / Math.PI).ToString());
          while (Math.Abs((v1.Angle(v2) * 180 / Math.PI - 90)) < PRECISION)
          {
            //vertices.Reverse();
            vertices.Insert(0, vertices[vertices.Count - 1]);
            vertices.RemoveAt(vertices.Count - 1);
            v2 = vertices[1].Subtract(vertices[0]);
          }
          */
          polygons.Add( vertices );

          i1++;
        }
      }

      return outermost != null;
    } // GetProfile
  }
}
