suppressPackageStartupMessages(library(ENmix));suppressPackageStartupMessages(library(minfi))
# Upstream getCGinfo assigns a scalar into a zero-row DataFrame when SNPs
# are absent. Replace only that assignment with a length-matched vector.
fn<-ENmix::getCGinfo
src<-deparse(body(fn));oldtxt<-'probesnpI$Infinium_Design_Type = "snpI"'
stopifnot(sum(grepl(oldtxt,src,fixed=TRUE))==1)
src<-gsub(oldtxt,'probesnpI$Infinium_Design_Type = rep("snpI", nrow(probesnpI))',src,fixed=TRUE)
body(fn)<-parse(text=src)[[1]];assignInNamespace('getCGinfo',fn,ns='ENmix')
# Cache annotation-only getCGinfo results, guarded by exact annotation and
# control-metadata equality. No intensity-dependent result is cached.
cg_original<-get('getCGinfo',asNamespace('ENmix'));cg_cache<-new.env()
cg_cached<-function(rgSet,type='IandII') {
 rd<-rowData(rgSet);ct<-metadata(rgSet)$ictrl
 if(is.null(cg_cache$rd)||!identical(rd,cg_cache$rd)||!identical(ct,cg_cache$ct)) {
  cg_cache$rd<-rd;cg_cache$ct<-ct;cg_cache$values<-list()
 }
 if(is.null(cg_cache$values[[type]]))cg_cache$values[[type]]<-cg_original(rgSet,type)
 cg_cache$values[[type]]
}
assignInNamespace('getCGinfo',cg_cached,ns='ENmix')
source('clockqc_v09/enmix_light_workers.R');processor<-make_light_enmix()
manifest<-ENmix:::getmanifest('IlluminaHumanMethylation450k','ilmn12.hg19')
convert<-function(rg){
 aa<-manifest$assay;cc<-manifest$ictrl;cc<-cc[cc$Address %in% rownames(rg),]
 tmp<-aa[seq_len(nrow(cc)),];tmp$Name<-paste0('ictrl',seq_len(nrow(cc)));tmp$Address<-cc$Address;tmp$Infinium_Design_Type<-'ctrl';tmp[,-which(names(tmp)%in%c('Name','Address','Infinium_Design_Type'))]<-NA
 aa<-rbind(aa,tmp);rownames(aa)<-aa$Address
 keep<-intersect(rownames(rg),rownames(aa));aa<-aa[keep,]
 z<-ENmix:::rgDataSet(Red=getRed(rg)[keep,,drop=FALSE],Green=getGreen(rg)[keep,,drop=FALSE],NBeads=assays(rg)$NBeads[keep,,drop=FALSE],rowData=as(aa,'DataFrame'),ictrl=as(cc,'DataFrame'))
 metadata(z)$Array<-'IlluminaHumanMethylation450k';metadata(z)$annotation<-'ilmn12.hg19';z
}
a<-commandArgs(TRUE);fixture<-length(a)==0||a[1]=='fixture'
files<-sort(list.files('raw_v09/GSE174422',pattern='_Grn.idat.gz$',full.names=TRUE));bases<-sub('_Grn.idat.gz$','',files)
if(fixture){
 rg<-read.metharray(bases[1:2],extended=TRUE);colnames(rg)<-sub('^GSM[0-9]+_','',basename(bases[1:2]));z<-convert(rg);raw<-getmeth(z);mr<-preprocessRaw(rg);common<-rownames(mr)
 stopifnot(nrow(raw)==485577,max(abs(getMeth(mr)-assays(raw)$Meth[common,]))==0,max(abs(getUnmeth(mr)-assays(raw)$Unmeth[common,]))==0)
 ex<-z[!grepl('^snp',rowData(z)$Infinium_Design_Type),]
 mx<-processor(ex,bgParaEst='oob',dyeCorr='RELIC',QCinfo=NULL,exQCsample=FALSE,exQCcpg=FALSE,nCores=2)
 old<-readRDS('processed_v09/GSE174422/enmix_intensity/001.rds');old<-old[,1:2]
 err<-max(abs(assays(mx)$Meth[rownames(old),]-getMeth(old)),abs(assays(mx)$Unmeth[rownames(old),]-getUnmeth(old)))
 writeLines(paste('native SNP-excluded vs saved minfi corrected intensity max error:',err),'clockqc_v010_run/fixture.txt');stopifnot(err<1e-7)
 saveRDS(z,'clockqc_v010_run/fixture_raw.rds');cat('FIXTURE PASS',err,'\n')
}else{
 dir.create('processed_v010',showWarnings=FALSE)
 for(start in seq(1,256,8)){
  out<-sprintf('processed_v010/%03d.rds',start);if(file.exists(out))next
  cat('SNP INCLUDED',start,'\n');flush.console();idx<-start:(start+7)
  rg<-read.metharray(bases[idx],extended=TRUE);colnames(rg)<-sub('^GSM[0-9]+_','',basename(bases[idx]));z<-convert(rg);rr<-getmeth(z);mm<-preprocessRaw(rg);stopifnot(nrow(rr)==485577,max(abs(getMeth(mm)-assays(rr)$Meth[rownames(mm),]))==0,max(abs(getUnmeth(mm)-assays(rr)$Unmeth[rownames(mm),]))==0);rm(rg,rr,mm);gc()
  m<-processor(z,bgParaEst='oob',dyeCorr='RELIC',QCinfo=NULL,exQCsample=FALSE,exQCcpg=FALSE,nCores=8)
  if(!file.exists('processed_v010/annotation.rds'))saveRDS(rowData(m),'processed_v010/annotation.rds');packed<-list(Meth=assays(m)$Meth,Unmeth=assays(m)$Unmeth);saveRDS(packed,paste0(out,'.partial'),compress=FALSE);stopifnot(file.rename(paste0(out,'.partial'),out));rm(z,m,packed);gc()
 }
}
