#################################
### COPYRIGHT: Anders Lyhagen ###
#################################

module CAUL_FAQS
module GridParser
  
  ###############################
  ###### PUBLIC INTERFACE #######
  ###############################
  @tg = nil
  @dh = nil
  @u_ind = 0
  @v_ind = 0
  
  #Parse a grid. The u axis extends between the corners p0 and p1 with p0 being origo.
  def self.parse(ents, p0, p1, tg) 
    @tg = tg
    
   #Extract the diagonals
    es, vs = getEV(ents)
    v0 = getVertex(vs, p0)
    @dh = getDiagonals(v0, es)
   
    #Parse grid
    vsc = getCorners(vs) #vsc = corner vertices
    v, e = getStartSet(p0, p1, vsc) #find start vertex and start edge
    grid = extractGrid(v, e, vsc)
    grid = reorientGrid(grid)
    quad_grid = QuadGrid::Grid.new(grid)
    return quad_grid
  end
        
  ###############################
  ###############################
  ###############################
  
  def self.getEV(ents)
    es = ents.grep(Sketchup::Edge)
    vs = es.map { |e| e.vertices }.flatten!.uniq!
    return es, vs
  end
  
  def self.getVertex(vs, p0)
    return vs.find { |v| v.position == p0 }
  end
  
  #extract faces, edges, vertices and corners
  def self.getCorners(vs)
    vsc = vs.select{ |v| thickEdgeCount(v) == 2} #corners
    return vsc
  end
  
  #interchange rows and columns in the 2D-array gr so that a 
  #gridcell is indexed gr[u][v]
  def self.reorientGrid(gr)
    grid = []
    (0..gr[0].length-1).each { |j| 
      col = []
      (0..gr.length-1).each { |i| col << gr[i][j] }
      grid << col
    }
    return grid
  end
  
  def self.getStartSet(p0, p1, vsc)
    v0 = vsc.select { |v| v.position == p0 }[0]
    v1 = vsc.select { |v| v.position == p1 }[0]
    #Error check here!!!
    v0.edges.each { |e|
      next if isInternal?(e)
      bp = extractBorderPath(v0, e, vsc)
      return bp[0][0], bp[0][1] if bp.last[1].other_vertex(bp.last[0]) == v1
    }
    return nil, nil
  end
  
  def self.extractGrid(v, e, vsc) 
    fh = {}
    rp = extractBorderPath(v, e, vsc)
    grid = []

    @v_ind = 0
    begin
      @u_ind = 0
      row = rp.each.map { |a| 
        quad = getCurrentQuad(a[0], a[1], fh)
        quad.fs.each { |f| fh[f] = nil }
        @u_ind += 1
        quad
      }
      grid << row
      rp = extractNextRowPath(row)
      @v_ind += 1
    end while !vsc.include?(rp[0][0])
    
    return grid
  end

  def self.extractBorderPath(v, e, vsc)
    bp = [[v, e]]
    #traverse border until next corner is found
    while !vsc.include?(v = e.other_vertex(v))
      e = v.edges.select{ |et| et != e && et.faces.length < 2 }[0]
      bp << [v, e]
    end
    return bp
  end

  def self.extractNextRowPath(row)
    return row.each.map { |quad| [quad.vs[3], quad.es[2]] }
  end
 
  def self.getCurrentQuad(v, e, fh)
    f = getUnprocessedFace(e, fh)
    return getQuad(v, e, f)
  end
  
  def self.getUnprocessedFace(e, fh)
    f = fh.has_key?(e.faces[0]) ? nil : e.faces[0]
    return f == nil ? e.faces[1] : f
  end
  
  def self.getQuad(v, e, f)
    cfs = getQuadFaces(f)
    
    #determine the kind
    kind = nil
    if @tg != nil && @u_ind < @tg.grid.length && @v_ind < @tg.grid[0].length
      kind = @tg.grid[@u_ind][@v_ind].kind
      #puts kind.to_s
    end
    
    ces = getQuadEdges(cfs)
    cl = getQuadLoop(v, e, ces)
    orientFaces(cl, cfs)
    quad = QuadGrid::Quad.new(cfs, cl, kind)
    return  quad
  end
  
  def self.orientFaces(cl, cfs)
    n0 = (cl[3][0].position - cl[0][0].position).cross(cl[1][0].position - cl[0][0].position)
    cfs[0].reverse! if n0.dot(cfs[0].normal) < 0
    if cfs.length == 2
      ei = cfs[0].outer_loop.edges.select { |e| isInternal?(e) }[0]
      cfs[1].reverse! if ei.reversed_in?(cfs[0]) == ei.reversed_in?(cfs[1]) 
    end
  end
  
  def self.getQuadFaces(f)
    return [f] + getOtherQuadFace(f)
  end
  
  def self.getOtherQuadFace(f)
    ei = f.outer_loop.edges.select { |e| isInternal?(e) }[0]
    return ei == nil ? [] : (ei.faces[0] != f ? [ei.faces[0]] : [ei.faces[1]])
  end
  
  def self.getQuadEdges(cfs)
    return cfs.map { |f| f.outer_loop.edges }.flatten!.select { |e| !isInternal?(e) }
  end
  
  def self.getQuadLoop(v, e, ces)
    loop = [[v, e]]
    ces.delete(e)
    (0..2).each { |i|
      v = loop[i][1].other_vertex(loop[i][0])
      e = ces.select { |e| e.used_by?(v) }[0]
      loop << [v, e]
      ces.delete(e)
    }    
    return loop
  end
  
  def self.thickEdgeCount(v)
    return v.edges.count { |e| !@dh.has_key?(e) }
  end
  

  def self.isInternal?(e)
    return @dh.has_key?(e)
    #return e.soft? #&& e.smooth? && !e.casts_shadows?
  end
  
  ################################
  ######## EXTRACT DIAGONALS #####
  ################################
  
  def self.getDiagonals(v, es)
    e = v.edges.find{ |e| e.faces.length == 1 }
    entry_set = [v, e]
    bh = getOuterBorders(es)
    dh = {} #hash with diagonals
    fh = {} #hash with faces
    extractDiagonals(entry_set, dh, bh, fh)
    return dh
  end
  
  #get the outer border (edges with only one face attached)
  def self.getOuterBorders(es)
    bh = {}
    es.each { |e| bh[e] = nil if e.faces.length == 1 }
    return bh
  end
  
  def self.extractDiagonals(entry_set, dh, bh, fh)
    nre = extractBorderRow(entry_set, dh, bh, fh) #nre = next row entries
    while true
      nre2 = []
      nre.each { |entry_set|
        nre2 << extractCell(entry_set, dh, bh, fh)
        return if nre2.last == nil
      }
      nre = nre2
    end
  end
  
  def self.extractBorderRow(entry_set, dh, bh, fh)
    nre = []
    begin
      nre << extractCell(entry_set, dh, bh, fh)
    end while (entry_set = getNextBorderEntry(entry_set, fh)) != nil
    return nre
  end
  
  def self.getNextBorderEntry(entry_set, fh)
    v0 = entry_set[1].other_vertex(entry_set[0])
    e0 = v0.edges.find { |e| e.faces.length == 1 && e != entry_set[1] && !fh.has_key?(e.faces[0]) }
    return nil if e0 == nil
    return [v0, e0]
  end
    
  def self.extractCell(ss, dh, bh, fh)
    v0  = ss[0] #entry vertex assignment
    eb0 = ss[1] #the initial cell border edge
    
    #find the unextracted face connected to eb0
    f0 = eb0.faces.find { |f| !fh.has_key?(f) }
    return nil if f0 == nil
    
    #find the other border edge connected to the start vertex.
    eb1 = v0.edges.find { |e| e != eb0 && bh.has_key?(e) && e.faces.count{ |f| !fh.has_key?(f) } == 1 }
    fh[f0] = nil #add face to hash
      
    #determine whether we have a pure quad or a triangulated quad
    if f0.edges.length == 3
      #determine the kind of triangulation:
      #Type 1: eb0 and eb1 connected to the same face.
      #Type 2: eb0 and ab1 connected to different faces
      if f0.edges.include?(eb0) && f0.edges.include?(eb1) #Type 1    
        dia = f0.edges.find { |e| e != eb0 && e != eb1 }
        dh[dia] = nil
        
        #Find other face
        f1 = dia.faces.find { |f| !fh.has_key?(f) }
        fh[f1] = nil #Add other face to face hash
        
        #find the rest of the border
        f1.edges.each { |e| bh[e] = nil unless e == dia }
      else #Type 2
        f1 = eb1.faces.find { |f| !fh.has_key?(f) }
        fh[f1] = nil
        
        dia = f1.edges.find { |e| f0.edges.include?(e) }
        dh[dia] = nil
        
        (f1.edges + f0.edges).each { |e| bh[e] = nil unless e == dia }
      end
    elsif f0.edges.length == 4 #pure quad
      fh[f0] = nil#add face to hash
      f0.edges.each { |e| bh[e] = nil }
      f1 = f0
    end
    
    #construct result for next row start set..
    v_res = eb1.other_vertex(v0)
    e_res = (f0.edges + f1.edges).find { |e| !dh.has_key?(e) && e != eb1 && e.vertices.include?(v_res) }
    
    return [v_res, e_res]
  end
    
  #####################################
  def self.main
    mod = Sketchup.active_model # Open model
    ent = mod.entities # All entities in model
    sel = mod.selection # Current selection
    
    g = sel.grep(Sketchup::Group)[0]
    qg = parse(g.entities, nil, nil, nil) 
    #puts qg.to_s
  end
end
end

