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

module CAUL_FAQS
module FlowInput

  ########################
  ###### CUT PLANES ######
  ########################
  
  def self.createCutPlanes(cg, pgr, pgr_out, tgr, bb, proj_ve, proj_pt)
   
    ups = tgr.v_map.inject([]) { |a, v|
      a << tgr.u_map.inject([]) { |b, u| 
        b << pgr.getPointOnGrid(u, v).transform(pgr_out) 
    }}
    vps = tgr.u_map.inject([]) { |a, u|
      a << tgr.v_map.inject([]) { |b, v| 
        b << pgr.getPointOnGrid(u, v).transform(pgr_out) 
    }}
    
    #reduce ups and vps
    ups2 = []
    vps2 = []
    ups.each { |up| ups2 << reduce(up) }
    vps.each { |up| vps2 << reduce(up) }
    mesh = Geom::PolygonMesh.new
    
    min_d, max_d = getMinMaxBBProj(bb, proj_pt, proj_ve)
    min_off = proj_ve.clone
    min_off.length = min_d - 1
    max_off = proj_ve.clone
    max_off.length = max_d + 1
        
    (0..ups2.length - 1).each { |i|
      (0..ups2[i].length - 2).each { |j|
        addFace2(cg, ups2[i][j] , ups2[i][j+1], min_off, max_off, mesh) 
    }}
    (0..vps2.length - 1).each { |i|
      (0..vps2[i].length - 2).each { |j|
        addFace2(cg, vps2[i][j] , vps2[i][j+1], min_off, max_off, mesh) 
    }}
    
    folds = tgr.getFoldsUV
    folds.each { |a|
      p0 = pgr.getPointOnGrid(a[0], a[1]).transform(pgr_out)
      p1 = pgr.getPointOnGrid(a[2], a[3]).transform(pgr_out)
      addFace2(cg, p0, p1, min_off, max_off, mesh) 
    }
    cg.entities.fill_from_mesh mesh
  end
  
  def self.reduce(ps)
    ps2 = [ps[0]]
    s_ind = j = 0
    while (i = getNextTurnInd(ps, s_ind)) != -1
      ps2 << ps[i]
      s_ind = i
      break if (j = j + 1) == 2000 #temp safeguard....
    end
    ps2 << ps.last
    return ps2
  end
  
  def self.getNextTurnInd(ps, s_ind)
    s_ve = ps[s_ind + 1] - ps[s_ind]
    (s_ind+1..ps.length - 2).each { |i|
      c_ve = ps[i + 1] - ps[i]
      return i unless c_ve.parallel?(s_ve)
    }
    return -1
  end
  
  def self.addFace2(ng, p00, p11, min_off, max_off, mesh) 
    p0 = offset(p00, max_off, 1)
    p1 = offset(p11, max_off, 1)
    p2 = offset(p11, min_off, 1)
    p3 = offset(p00, min_off, 1)
    
    mesh.add_point p0; mesh.add_point p1;
    mesh.add_point p2; mesh.add_point p3;
    mesh.add_polygon p0, p1, p2, p3
  end
  
  def self.offset(p, v, d)
    pc = p.clone
    pc.x = pc.x + v.x * d
    pc.y = pc.y + v.y * d
    pc.z = pc.z + v.z * d
    return pc
  end
  
  def self.getMinMaxBBProj(bb, p, ve)
    min_d = Float::MAX
    max_d = Float::MIN
    (0..7).each { |i|
      d = ( bb.corner(i) - p).dot(ve)
      min_d = d < min_d ? d : min_d
      max_d = d > max_d ? d : max_d
    }
    return min_d, max_d
  end
  

  def self.getBounds(gs)
    bb = Geom::BoundingBox.new
    gs.each { |g| bb.add(g.bounds.min); bb.add(g.bounds.max) }
    return bb
  end
  
  ########################
  ######### AUX ##########
  ########################
  
  def self.formatString(s, len) 
    sp = ''
    (0..(len - s.length - 1)).each { |i| sp += ' ' }
    return s + sp
  end
  
  #Observe, since we use fd.proj_tp as fix point, there is no need to change this point
  #it will remain on the projgrid...
  #the geometry groups are scaled in cut..
  def self.scaleSupport(fd, scale_up)
    s = scale_up ? fd.scale : 1.0 / fd.scale
    #apply scale transformation to the support group
    #recompute all transformations (use fd.project_tp as fix point)
    tr = Geom::Transformation.scaling(fd.proj_pt, s, s, s)
    fd.sg.transform!(tr)
    
    #recalculate the common bb for all groups
    nbb = Geom::BoundingBox.new
    nbb.add(fd.bb.min.transform(tr))
    nbb.add(fd.bb.max.transform(tr))
    fd.bb = nbb
    
    fd.pgr_out = fd.sg.transformation * fd.pg.transformation
    fd.pgr_in =  fd.pg.transformation.inverse * fd.sg.transformation.inverse
    fd.tgr_out = fd.sg.transformation * fd.tg.transformation
    fd.tgr_in =  fd.tg.transformation.inverse * fd.sg.transformation.inverse
  end
  
  def self.scaleBackGeom(fd)
    fd.ggs.each { |g| 
      Cut.scale(g, 1.0 / fd.scale, fd.proj_pt) 
      #add and remove a dummy point to force the recalculation of the bounding box..
      #this is due to a bug in the api.
      cp = g.entities.add_cpoint([0, 0, 0])
      cp.erase!
   }
  end
  
  ########################
  ########################
  ########################
  
  
  def self.cut(ent, fd)
    cg = ent.add_group
    createCutPlanes(cg, fd.pgr, fd.pgr_out, fd.tgr, fd.bb, fd.proj_ve, fd.proj_pt)
    fd.ggs.each { |g|
      gg_bb = Cut.scale(g, fd.scale, fd.proj_pt)
      Cut.cutBB(ent, cg, g, cg.bounds, gg_bb)
    }
    cg.erase!
  end
  
  def self.flowAlong(ent, fd)
    to_erase = []
    fd.ggs.each_with_index { |g, i|
      res_g = ent.add_group
      Flow.flowAlong(res_g, g, fd.pgr, fd.pgr_in, fd.tgr, fd.tgr_out, fd.proj_ve)
      to_erase[i] = fd.ggs[i]
      fd.ggs[i] = res_g
    }
    to_erase.each { |g| g.erase! }
  end
  
  def self.getErrorString()
    return @ERROR_STRING
  end
  
  #mode == 0 : cut and flow
  #mode == 1 : cut
  #mode == 2 : flow
  def self.flowMain(ent, sel, mode) 
    fd = Input.parseInput(sel, ent);
    
    if fd == nil
      @ERROR_STRING = Input.getErrorString()
      return false
    end
    
    scaleSupport(fd, true) unless mode == 2
    cut(ent, fd) if mode == 0 || mode == 1
    flowAlong(ent, fd) if mode == 0 || mode == 2
    scaleSupport(fd, false) unless mode == 2
    scaleBackGeom(fd) unless mode == 2
    return true
  end
  
  ########################
  ######## MAIN ##########
  ########################
   
  def self.main(mode)

    mod = Sketchup.active_model
    ent = mod.entities
    sel = mod.selection
    
    mod.start_operation("FlowAlongQuadGrid", true)
    status = flowMain(ent, sel, mode)
    return false if status == false
    mod.commit_operation
    return true
  end
  
end
end
