// Copyright 2021 Molecula Corp. All rights reserved.
// PURPOSE: Generate data for Samsung unique workflow/use case, as described in Jira Ticket FB-971
// INPUT: none
// OUTPUT: 6 csv files, containing approx 1 billion lines of data associated to 200 million unique records (approx 28BGB of data)

package main

import (
	"bufio"
	"fmt"
	"log"
	"math/rand"
	"os"
	"strconv"
	"time"

	"github.com/pkg/errors"
)

var countryList [246]string = [...]string{"ABW", "AFG", "AGO", "AIA", "ALA", "ALB", "AND", "ANT", "ARE", "ARG",
	"ARM", "ASM", "ATA", "ATF", "ATG", "AUS", "AUT", "AZE", "BDI", "BEL", "BEN", "BFA", "BGD", "BGR", "BHR", "BHS",
	"BIH", "BLM", "BLR", "BLZ", "BMU", "BOL", "BRA", "BRB", "BRN", "BTN", "BVT", "BWA", "CAF", "CAN", "CCK", "CHE",
	"CHL", "CHN", "CIV", "CMR", "COD", "COG", "COK", "COL", "COM", "CPV", "CRI", "CUB", "CXR", "CYM", "CYP", "CZE",
	"DEU", "DJI", "DMA", "DNK", "DOM", "DZA", "ECU", "EGY", "ERI", "ESH", "ESP", "EST", "ETH", "FIN", "FJI", "FLK",
	"FRA", "FRO", "FSM", "GAB", "GBR", "GEO", "GGY", "GHA", "GIB", "GIN", "GLP", "GMB", "GNB", "GNQ", "GRC", "GRD",
	"GRL", "GTM", "GUF", "GUM", "GUY", "HKG", "HMD", "HND", "HRV", "HTI", "HUN", "IDN", "IMN", "IND", "IOT", "IRL",
	"IRN", "IRQ", "ISL", "ISR", "ITA", "JAM", "JEY", "JOR", "JPN", "KAZ", "KEN", "KGZ", "KHM", "KIR", "KNA", "KOR",
	"KWT", "LAO", "LBN", "LBR", "LBY", "LCA", "LIE", "LKA", "LSO", "LTU", "LUX", "LVA", "MAC", "MAF", "MAR", "MCO",
	"MDA", "MDG", "MDV", "MEX", "MHL", "MKD", "MLI", "MLT", "MMR", "MNE", "MNG", "MNP", "MOZ", "MRT", "MSR", "MTQ",
	"MUS", "MWI", "MYS", "MYT", "NAM", "NCL", "NER", "NFK", "NGA", "NIC", "NIU", "NLD", "NOR", "NPL", "NRU", "NZL",
	"OMN", "PAK", "PAN", "PCN", "PER", "PHL", "PLW", "PNG", "POL", "PRI", "PRK", "PRT", "PRY", "PSE", "PYF", "QAT",
	"REU", "ROU", "RUS", "RWA", "SAU", "SDN", "SEN", "SGP", "SGS", "SHN", "SJM", "SLB", "SLE", "SLV", "SMR", "SOM",
	"SPM", "SRB", "STP", "SUR", "SVK", "SVN", "SWE", "SWZ", "SYC", "SYR", "TCA", "TCD", "TGO", "THA", "TJK", "TKL",
	"TKM", "TLS", "TON", "TTO", "TUN", "TUR", "TUV", "TWN", "TZA", "UGA", "UKR", "UMI", "URY", "USA", "UZB", "VAT",
	"VCT", "VEN", "VGB", "VIR", "VNM", "VUT", "WLF", "WSM", "YEM", "ZAF", "ZMB", "ZWE",
}

const totalRecords int = 200000000

func main() {
	log.Println("generating age field...")
	if err := GenerateAgeField(totalRecords); err != nil {
		log.Fatalf("unable to generate age field: %v", err)
	}
	log.Println("generating age field done.")

	log.Println("generating ip field...")
	if err := GenerateIPField(totalRecords); err != nil {
		log.Fatalf("unable to generate IP field: %v", err)
	}
	log.Println("generating ip field done.")

	log.Println("generating identifier field...")
	if err := GenerateArbIdField(totalRecords); err != nil {
		log.Fatalf("unable to generate indentifier field: %v", err)
	}
	log.Println("generating identifier field done.")

	log.Println("generating opt in field...")
	if err := GenerateOptInField(totalRecords); err != nil {
		log.Fatalf("unable to generate opt in field: %v", err)
	}
	log.Println("generating opt in field done.")

	log.Println("generating country field...")
	if err := GenerateCountryField(totalRecords); err != nil {
		log.Fatalf("unable to generate country field: %v", err)
	}
	log.Println("generating country field done.")

	log.Println("generating time field...")
	if err := GenerateTimeField(totalRecords); err != nil {
		log.Fatalf("unable to generate time field: %v", err)
	}
	log.Println("generating time field done.")
}

func GenerateAgeField(requestedRecords int) error {
	csvFile, err0 := os.Create("age.csv")
	if err0 != nil {
		return errors.Wrap(err0, "unable to create age.csv")
	}

	writer := bufio.NewWriter(csvFile)

	for i := 0; i < requestedRecords; i++ {
		if rand.Intn(100) <= 20 {
			_, err := writer.WriteString(strconv.Itoa(i) + "," + strconv.Itoa(rand.Intn(88)+13) + "\n")
			if err != nil {
				return errors.Wrap(err, "unable to write to age.csv")
			}

			if rand.Intn(10) == 1 {
				_, err := writer.WriteString(strconv.Itoa(i) + "," + strconv.Itoa(rand.Intn(88)+13) + "\n")
				if err != nil {
					return errors.Wrap(err, "unable to write to age.csv")

				}
			}
		}
		if i%2000000 == 0 {
			log.Printf("generating age field (%d)", i)
		}
	}
	err1 := writer.Flush()
	if err1 != nil {
		return errors.Wrap(err1, "unable to flush writer")
	}

	err2 := csvFile.Close()
	if err2 != nil {
		return errors.Wrap(err2, "unable to close age.csv")
	}
	return nil
}

func GenerateIPField(requestedRecords int) error {
	csvFile, err0 := os.Create("ip.csv")
	if err0 != nil {
		return errors.Wrap(err0, "unable to create ip.csv")
	}

	writer := bufio.NewWriter(csvFile)

	for i := 0; i < requestedRecords; i++ {
		_, err := writer.WriteString(strconv.Itoa(i) + "," + fmt.Sprint(rand.Int31()) + "\n")
		if err != nil {
			return errors.Wrap(err, "unable to write to ip.csv")
		}

		if rand.Intn(10) == 1 {
			_, err := writer.WriteString(strconv.Itoa(i) + "," + fmt.Sprint(rand.Int31()) + "\n")
			if err != nil {
				return errors.Wrap(err, "unable to write to ip.csv")
			}
		}
		if i%2000000 == 0 {
			log.Printf("generating ip field (%d)", i)
		}
	}

	err1 := writer.Flush()
	if err1 != nil {
		return errors.Wrap(err1, "unable to flush writer")
	}

	err2 := csvFile.Close()
	if err2 != nil {
		return errors.Wrap(err2, "unable to close ip.csv")
	}
	return nil

}

func GenerateArbIdField(requestedRecords int) error {
	csvFile, err0 := os.Create("identifier.csv")
	if err0 != nil {
		return errors.Wrap(err0, "unable to create identifier.csv")
	}

	writer := bufio.NewWriter(csvFile)

	for i := 0; i < requestedRecords; i++ {
		_, err := writer.WriteString(strconv.Itoa(i) + "," + fmt.Sprint(rand.Int63()) + "\n")
		if err != nil {
			return errors.Wrap(err, "unable to write to identifier.csv")
		}

		if rand.Intn(10) == 1 {
			_, err := writer.WriteString(strconv.Itoa(i) + "," + fmt.Sprint(rand.Int63()) + "\n")
			if err != nil {
				return errors.Wrap(err, "unable to write to identifier.csv")
			}
		}
		if i%2000000 == 0 {
			log.Printf("generating identifier field (%d)", i)
		}
	}

	err1 := writer.Flush()
	if err1 != nil {
		return errors.Wrap(err1, "unable to flush writer")
	}

	err2 := csvFile.Close()
	if err2 != nil {
		return errors.Wrap(err2, "unable to close identifier.csv")
	}
	return nil

}

func GenerateTimeField(requestedRecords int) error {
	csvFile, err0 := os.Create("time.csv")
	if err0 != nil {
		return errors.Wrap(err0, "unable to create time.csv")
	}

	writer := bufio.NewWriter(csvFile)

	r := rand.New(rand.NewSource(rand.Int63()))
	s := 1.01
	v := 1.01
	var imax uint64 = 4000000

	distro := rand.NewZipf(r, s, v, imax)

	for i := 0; i < requestedRecords; i++ {
		value := distro.Uint64()
		urlString := "https://www.test.com/" + strconv.FormatUint(value, 10)

		date := generateDate()

		_, err := writer.WriteString(urlString + "," + strconv.Itoa(i) + "," + date + "\n")
		if err != nil {
			return errors.Wrap(err, "unable to write to time.csv")
		}

		if rand.Intn(5) == 1 {
			date = generateDate()
			_, err := writer.WriteString(urlString + "," + strconv.Itoa(i) + "," + date + "\n")
			if err != nil {
				return errors.Wrap(err, "unable to write to time.csv")
			}
		}

		if rand.Intn(10) == 1 {
			value := distro.Uint64()
			urlString = "https://www.test.com/" + strconv.FormatUint(value, 10)
			date = generateDate()
			_, err := writer.WriteString(urlString + "," + strconv.Itoa(i) + "," + date + "\n")
			if err != nil {
				return errors.Wrap(err, "unable to write to time.csv")
			}
		}
		if i%2000000 == 0 {
			log.Printf("generating time field (%d)", i)
		}
	}
	err1 := writer.Flush()
	if err1 != nil {
		return errors.Wrap(err1, "unable to flush writer")
	}

	err2 := csvFile.Close()
	if err2 != nil {
		return errors.Wrap(err2, "unable to close time.csv")
	}
	return nil

}

func GenerateOptInField(requestedRecords int) error {
	csvFile, err0 := os.Create("optin.csv")
	if err0 != nil {
		return errors.Wrap(err0, "unable to create optin.csv")
	}

	writer := bufio.NewWriter(csvFile)

	var optString string

	for i := 0; i < requestedRecords; i++ {

		if rand.Intn(20) == 1 {
			optString = "1"
		} else {
			optString = "0"
		}

		_, err := writer.WriteString(optString + "," + strconv.Itoa(i) + "\n")
		if err != nil {
			return errors.Wrap(err, "unable to write to optin.csv")
		}

		if rand.Intn(10) == 1 {
			if optString == "0" {
				optString = "1"
				_, err := writer.WriteString(optString + "," + strconv.Itoa(i) + "\n")
				if err != nil {
					return errors.Wrap(err, "unable to write to optin.csv")
				}
			} else {
				optString = "0"
				_, err := writer.WriteString(optString + "," + strconv.Itoa(i) + "\n")
				if err != nil {
					return errors.Wrap(err, "unable to write to optin.csv")
				}
			}
		}
		if i%2000000 == 0 {
			log.Printf("generating opt in field (%d)", i)
		}
	}

	err1 := writer.Flush()
	if err1 != nil {
		return errors.Wrap(err1, "unable to flush writer")
	}

	err2 := csvFile.Close()
	if err2 != nil {
		return errors.Wrap(err2, "unable to close optin.csv")
	}
	return nil

}

func GenerateCountryField(requestedRecords int) error {
	csvFile, err0 := os.Create("country.csv")
	if err0 != nil {
		return errors.Wrap(err0, "unable to create country.csv")
	}

	writer := bufio.NewWriter(csvFile)

	// populate countries
	r := rand.New(rand.NewSource(rand.Int63()))
	s := 1.01
	v := 1.01
	var imax uint64 = 245

	distro := rand.NewZipf(r, s, v, imax)

	for i := 0; i < requestedRecords; i++ {
		if rand.Intn(2) == 1 {
			value := distro.Uint64()
			country := countryList[value]
			_, err := writer.WriteString(country + "," + strconv.Itoa(i) + "\n")
			if err != nil {
				return errors.Wrap(err, "unable to write to country.csv")
			}

			if rand.Intn(10) == 1 {
				value = distro.Uint64()
				country = countryList[value]
				_, err := writer.WriteString(country + "," + strconv.Itoa(i) + "\n")
				if err != nil {
					return errors.Wrap(err, "unable to write to country.csv")
				}
			}
		}
		if i%2000000 == 0 {
			log.Printf("generating country field (%d)", i)
		}
	}

	err1 := writer.Flush()
	if err1 != nil {
		return errors.Wrap(err1, "unable to flush writer")
	}

	err2 := csvFile.Close()
	if err2 != nil {
		return errors.Wrap(err2, "unable to close country.csv")
	}
	return nil

}

func generateDate() string {
	// Nov 22, 2021 = 1637616960
	// twenty years =  631138520
	// date min 	= 1006478440
	date := rand.Int63n(631138520) + 1006478440

	t := time.Unix(date, 0)
	YMDstring := t.Format("2006-01-02T15:04")

	return YMDstring
}
