package main
// Standard version uint64 numbers (upto ca 18 decimal digits)
import (
    "runtime"
    "fmt"
    "os"
    "strings"
    "strconv"
    "time"
    "math"
    "bufio"
    "sync"
)
const minseg = 100

var Sin string
var ncore int
var thestart time.Time
var wloops sync.WaitGroup
var bigch string

func getNin() uint64 {  // Inputs
var err error
var Nb uint64
var sinput string
var scpu string
var onum int
var maxcpus int
maxcpus = runtime.GOMAXPROCS(0)
scanner := bufio.NewScanner(os.Stdin)
for {
    fmt.Printf("\nEnter N (separators allowed,  0 to exit)\n==> ")
    if scanner.Scan() {
        sinput = scanner.Text()
    }
    if err := scanner.Err(); err != nil {
        fmt.Println("Input rejected... Retry\n")
    } else {
        bsinput := []byte(sinput)
        if len(sinput) > 0 {
            if (int(bsinput[0]) - 48) == 0 {
                os.Exit(0)
            }
        }
        Sin = ""
        for _,ch := range bsinput {
            onum = int(ch)-48
            if (onum >= 0) && (onum <= 9) {
                Sin += string(ch)
            }
        }
        if (len(Sin) <= 0) {
            fmt.Println("\nBad entry... Retry\n")
        } else {
            if (len(Sin) > 18) {
                fmt.Println("\nNo more than 18 digits !... Retry\n")
            } else { break }
        }
    }
}

//fmt.Println("OK ",Sin)

Nb,err = strconv.ParseUint(Sin,0,64)
if err != nil {
    fmt.Println("But N is really too big !")
    os.Exit(1)
}

for {
    fmt.Printf("\nParallel cores 1 to " +strconv.Itoa(maxcpus)+" (default)] : ")
    if scanner.Scan() {
        sinput = scanner.Text()
    }
    if err := scanner.Err(); err != nil {
        fmt.Println("Input rejected... Retry\n")
    } else {
        scpu = ""
        if len(sinput) > 0 {
            bsinput := []byte(sinput)
            for _,ch := range bsinput {
                onum = int(ch)-48
                if (onum >= 0) && (onum <= 9) {
                    scpu += string(ch)
                }
            }
            onum,_ = strconv.Atoi(scpu)
            if onum <= maxcpus && onum > 0 {
                ncore = onum
                break
            } else {
                fmt.Println("\nBad entry, retry...\n")
            }
        } else {
            ncore = maxcpus
            break
        }
    }
}

onum = 0
for onum < maxcpus {     // create + void Logx.txt
    scpu = "Log" + strconv.Itoa(onum) + ".txt"
    if _,err := os.Create(scpu); err !=nil {
       fmt.Println("File system error")
       os.Exit(1)
     }
    onum += 1
}
return Nb
}


func Retbuild() {     // Collect Logx.txt files
var icore int
var fname string
var stempup, stempdn string
var bigchup string
var bigchdn string
var bok bool
wloops.Wait()
icore = 0
for icore < ncore {
  fname = "Log" + strconv.Itoa(icore) + ".txt"
  bs,_:= os.ReadFile(fname)
  stempup, stempdn, bok = strings.Cut(string(bs),"_")
  if bok {
    bigchup += stempup
    bigchdn = stempdn + bigchdn
  }
  icore += 1
}
bigch = bigchup + bigchdn
}

func Theloop(thecpu int, theN uint64, thefloor uint64, theceiling uint64, theinc uint64) {
// Routine in seach of divisors in allocated segment ==> Logx.txt
// x = thread  number (thecpu)
var test, ghost uint64
var chup, chdn string
defer wloops.Done()
test = thefloor
if test == 1 {
    chup = " 1"
    if theN > 1 {
        chdn = " " + strconv.FormatUint(theN,10)
    }
}
for {
    test += theinc
    if test > theceiling { break }
    if theN % test == 0 {
        ghost = theN / test
        chup += " " + strconv.FormatUint(test,10)
        if ghost > test {
            chdn = " " + strconv.FormatUint(ghost,10) + chdn
        }
    }
}
chup += "_" + chdn
os.WriteFile("Log"+strconv.Itoa(thecpu)+".txt",[]byte(chup), 0666)
}

func Noteven() bool {
if (Sin[len(Sin)-1]-48)%2 == 0 {
    return false
} else {
    return true
}
}

func Gloops(Nbig uint64) {
// defines the search segment for each goroutine (thread)
// launches the goroutines
var candfloor, candceiling, finalcandceiling, increment, segment uint64
var icore int
if Noteven() {
  increment = 2
} else {
  increment = 1
}
candfloor = 1
candceiling = uint64(math.Floor(math.Sqrt(float64(Nbig)+0.5)))
finalcandceiling = candceiling
segment = candceiling / uint64(ncore)
for {
  if (segment < minseg) && (ncore > 1) {
     ncore = ncore - 1
     segment = candceiling / uint64(ncore)
  } else { break }
}
if (segment < minseg) || (ncore == 1) {
  ncore = 1
  candceiling = finalcandceiling
} else {
  if (increment == 2) && (segment % 2 > 0) {
     segment = segment + 1
  }
  candceiling = candfloor + segment
}

icore = 0
fmt.Println("\nEXEC with nr of parallel routines = ",ncore)
thestart = time.Now()   // start
for icore < ncore {
  wloops.Add(1)
  go Theloop(icore,Nbig,candfloor,candceiling, increment)
  icore += 1
  candfloor = candceiling
  if icore < (ncore - 1) {
    candceiling = candceiling + segment
  } else {
    candceiling=finalcandceiling
  }
}

}


func main() {
var theend time.Time
var N uint64
var ndiv int
for {
    N = getNin()
    Gloops(N)
    Retbuild()
    ndiv = strings.Count(bigch," ")
    theend = time.Now()             // stop
    bigch = "\n[" + strconv.FormatInt(theend.Sub(thestart).Milliseconds(),10) + " ms // " + strconv.Itoa(ncore) + " cores ==> " + strconv.Itoa(ndiv) + " divisors]\n" + bigch + "\n"
    f,_ := os.OpenFile("Divisors.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if _, err := f.Write([]byte(bigch)); err != nil {
            f.Close()
    }
    fmt.Println(bigch)
    fmt.Print("\nResults saved in Divisors.txt\nPress any key to exit")
    fmt.Scanf("%s", &bigch)
}
}
