package main

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)

type FileInfo struct {
	FilePath string
	FileName string
	FileSize int64
	FileTime time.Time
}

var (
	diffYear           int    = 0
	bkupPath           string = ""
	autoBkup           bool   = false
	exclude            bool   = false
	aodType            string = ""
	aodPath            string = ""
	modPath            string = ""
	skipFile           string = ""
	aodList                   = make([]string, 0)
	criList                   = make([]string, 0)
	moduleList                = make([]string, 0)
	skipList                  = make([]string, 0)
	bkupList                  = make([]string, 0)
	uniqueCriList             = make([]string, 0)
	uniqueModuleList          = make([]string, 0)
	needlessModuleList        = make([]string, 0)
	needlessModuleInfo        = make([]string, 0)
	missingModuleList         = make([]string, 0)
	errAOD                    = make([]string, 0)
	errCRI                    = make([]string, 0)
	aodInfoList        []FileInfo
	moduleInfoList     []FileInfo
)

var regFilter = `.*`
var regLNB = `(?i)\S{10,}\.[0-9]{3}`
var regTNB = `(?i)SSW\S{7}\.SSW\S{7}`
var regSBID = `(?i)\S{8,}\.DAT`

func init() {
	flag.BoolVar(&exclude, "x", false, "Exclude Mode")
	flag.BoolVar(&autoBkup, "y", false, "Auto Backup")
	flag.IntVar(&diffYear, "d", 6, "Differ Year Of File Time to Backup")
	flag.StringVar(&aodPath, "a", `D:\Vol1\PDLINE\PLANT1\PRELOAD\AOD`, "Path Of AODs")
	flag.StringVar(&modPath, "m", `D:\COMMON`, "Path Of Modules")
	flag.StringVar(&bkupPath, "b", `D:\COMMON_BKUP`, "Path Of Backup")
	flag.StringVar(&aodType, "t", "*", "Type Of AODs, LNB/TNB/SBID/*")
	flag.StringVar(&skipFile, "s", "./SkipList.txt", "Text File Of Skip Modules")
	flag.Parse()
}

func main() {
	log.SetOutput(os.Stdout)
	log.Printf("[MSG]  Commandline:  %v\r\n", os.Args)
	bkupList = append(bkupList, fmt.Sprintf("### Commandline:  %v", os.Args))

	_, err := os.Stat(aodPath)
	if err != nil {
		log.Printf("[ERR]  Path Of AODs Not Exists.\r\n")
		os.Exit(1)
	}
	_, err = os.Stat(modPath)
	if err != nil {
		log.Printf("[ERR]  Path Of Modules Not Exists.\r\n")
		os.Exit(1)
	}

	if skipFile != "" {
		obj, err := os.Open(skipFile)
		if err == nil {
			buf := bufio.NewScanner(obj)
			for buf.Scan() {
				if buf.Err() != nil {
					break
				}
				skipList = append(skipList, strings.TrimSpace(buf.Text()))
			}
		}
	}
	log.Printf("[MSG]  Skip Modules: %d\r\n", len(skipList))

	log.Printf("[MSG]  Summary Loacal AODs ...\r\n")
	aodInfoList, err = _SummaryAODs(aodPath, aodType)
	if err != nil {
		log.Printf("[ERR]  Get AOD List Failed: %s\r\n", err.Error())
		os.Exit(1)
	}
	log.Printf("[MSG]  Total Local AODs: %d\r\n", len(aodInfoList))
	_WriteFileFromStringSlice("./AOD.txt", aodList)

	log.Printf("[MSG]  Summary Local Modules ...\r\n")
	moduleInfoList, err = _SummaryModules(modPath)
	if err != nil {
		log.Printf("[ERR]  Get Module List Failed: %s\r\n", err.Error())
		os.Exit(1)
	}
	log.Printf("[MSG]  Total Local Modules: %d\r\n", len(moduleInfoList))

	regCRI, err := regexp.Compile(`(?i).*File=(.{1,}.CRI)`)
	if err != nil {
		log.Printf("[ERR]  RegExp - CRI Compile Error: %s\r\n", err.Error())
		os.Exit(1)
	}

	log.Printf("[MSG]  Summary CRIs From AODs ...\r\n")
	for _, aod := range aodList {
		rdr, err := os.Open(aod)
		if err != nil {
			errAOD = append(errAOD, aod)
			continue
		}
		buf := bufio.NewScanner(rdr)
		for buf.Scan() {
			if buf.Err() != nil {
				break
			}
			rst := regCRI.FindStringSubmatch(buf.Text())
			if len(rst) != 2 {
				continue
			}
			criList = append(criList, strings.TrimSpace(rst[1]))
		}
	}
	log.Printf("[MSG]  Total CRIs From AODs: %d\r\n", len(criList))
	uniqueCriList = _RemoveDuplicateElementOfSlice(criList)
	log.Printf("[MSG]  Unique CRIs From AODs: %d\r\n", len(uniqueCriList))

	regModuleName, err := regexp.Compile(`(?i)(ModuleName[0-9]{0,}|ModuleThis[0-9]{0,})=(.{1,})`)
	if err != nil {
		log.Printf("[ERR]  RegExp - ModuleName Compile Error: %s\r\n", err.Error())
		os.Exit(1)
	}

	log.Printf("[MSG]  Summary Modules From CRIs ...\r\n")
	for _, cri := range uniqueCriList {
		rdr, err := os.Open(modPath + "\\" + cri)
		if err != nil {
			errCRI = append(errCRI, modPath+"\\"+cri)
		}
		buf := bufio.NewScanner(rdr)
		for buf.Scan() {
			if buf.Err() != nil {
				break
			}
			rst := regModuleName.FindStringSubmatch(buf.Text())
			if len(rst) != 3 {
				continue
			}
			moduleList = append(moduleList, strings.TrimSpace(rst[2]))
		}
	}
	log.Printf("[MSG]  Total Modules From CRIs: %d\r\n", len(moduleList))
	uniqueModuleList = _RemoveDuplicateElementOfSlice(moduleList)
	log.Printf("[MSG]  Unique Modules From CRIs: %d\r\n", len(uniqueModuleList))
	_WriteFileFromStringSlice("./UniqueModules.txt", uniqueModuleList)

	log.Printf("[MSG]  Checking Missing Modules ...\r\n")
	for _, m := range uniqueModuleList {
		exist := false
		for _, l := range moduleInfoList {
			if strings.EqualFold(m, l.FileName) {
				exist = true
				break
			}
		}
		if !exist {
			missingModuleList = append(missingModuleList, m)
		}
	}

	log.Printf("[MSG]  Checking Needless Modules ...\r\n")
	for _, l := range moduleInfoList {
		needless := true
		for _, m := range uniqueModuleList {
			if strings.EqualFold(l.FileName, m) {
				needless = false
				break
			}
		}
		if needless {
			needlessModuleList = append(needlessModuleList, l.FileName)
			needlessModuleInfo = append(needlessModuleInfo, l.FileName+"\t"+l.FileTime.Format("2006-01-02 15:04:05"))
		}
	}

	log.Printf("*****  Error AODs: %d\r\n", len(errAOD))
	_WriteFileFromStringSlice("./errAOD.txt", errAOD)

	log.Printf("*****  Error CRIs: %d\r\n", len(errCRI))
	_WriteFileFromStringSlice("./errCRI.txt", errCRI)

	log.Printf("#####  Missing Modules: %d\r\n", len(missingModuleList))
	_WriteFileFromStringSlice("./MissingModules.txt", missingModuleList)

	log.Printf("#####  Needless Modules: %d\r\n", len(needlessModuleList))
	_WriteFileFromStringSlice("./NeedlessModules.txt", needlessModuleInfo)

	if autoBkup {
		log.Printf("[MSG]  Starting Backup Modules Older Than %d Years ...\r\n", diffYear)
		_, err := os.Stat(bkupPath)
		if err != nil {
			err = os.MkdirAll(bkupPath, os.ModePerm)
			if err != nil {
				log.Printf("[ERR]  Create Backup Directory Failed.\r\n")
				os.Exit(1)
			}
		}
		ttl := 0
	LOOP:
		for _, n := range needlessModuleList {
			for _, s := range skipList {
				if s == n {
					log.Printf("[MSG]  Skip Backup Module: %s\r\n", s)
					continue LOOP
				}
			}
			for _, m := range moduleInfoList {
				if m.FileName == n {
					if m.FileTime.AddDate(diffYear, 0, 0).Before(time.Now()) {
						err := os.Rename(m.FilePath, bkupPath+"\\"+m.FileName)
						if err != nil {
							log.Printf("[ERR]  FileName: %s;\tErrMsg: %s\r\n", m.FileName, err.Error())
							break
						}
						bkupList = append(bkupList, m.FileName+"\t"+m.FileTime.Format("2006-01-02 15:04:05"))
						log.Printf("[MSG]  FileName: %s;\tFileTime: %s\r\n", m.FileName, m.FileTime.Format("2006-01-02 15:04:05"))
						ttl += 1
						break
					}
				}
			}
		}
		_WriteFileFromStringSlice("./Backup_"+time.Now().Format("2006-01-02_15-04-05")+".txt", bkupList)
		log.Printf("[MSG]  Total Backed Up Modules: %d\r\n", ttl)
	}
}

func _SummaryAODs(basePath, filter string) ([]FileInfo, error) {
	var err error
	sortInfoList := make([]FileInfo, 0)
	fileInfoList := make([]FileInfo, 0)

	err = filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			fi := new(FileInfo)
			fi.FilePath = path
			fi.FileName = info.Name()
			fi.FileSize = info.Size()
			fi.FileTime = info.ModTime() //.Format("2006-01-02 15:04:05")
			fileInfoList = append(fileInfoList, *fi)
			aodList = append(aodList, fi.FilePath)
		}
		return nil
	})
	if err != nil {
		log.Printf("[ERR]  Get File List Failed: %s\r\n", err.Error())
		return nil, err
	}

	switch filter {
	case "LNB":
		regFilter = regLNB
	case "TNB":
		regFilter = regTNB
	case "SBID":
		regFilter = regSBID
	default:
		return fileInfoList, nil
	}

	for _, i := range fileInfoList {
		reg := regexp.MustCompile(regFilter)
		rst := reg.FindStringSubmatch(i.FileName)

		if exclude {
			if len(rst) == 1 {
				continue
			}
			sortInfoList = append(sortInfoList, i)
		}

		if !exclude {
			if len(rst) != 1 {
				continue
			}
			sortInfoList = append(sortInfoList, i)
		}
	}
	return sortInfoList, nil
}

func _SummaryModules(basePath string) ([]FileInfo, error) {
	var err error
	fileInfoList := make([]FileInfo, 0)

	err = filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			fi := new(FileInfo)
			fi.FilePath = path
			fi.FileName = info.Name()
			fi.FileSize = info.Size()
			fi.FileTime = info.ModTime() //.Format("2006-01-02 15:04:05")
			if path == basePath+"\\"+info.Name() {
				fileInfoList = append(fileInfoList, *fi)
			}
		}
		return nil
	})
	if err != nil {
		log.Printf("[ERR]  Get File List Failed: %s\r\n", err.Error())
		return nil, err
	}
	return fileInfoList, nil
}

func _RemoveDuplicateElementOfSlice(src []string) []string {
	dst := make([]string, 0, len(src))
	tmp := map[string]struct{}{}
	for _, s := range src {
		if _, ok := tmp[s]; !ok {
			tmp[s] = struct{}{}
			dst = append(dst, s)
		}
	}
	return dst
}

func _WriteFileFromStringSlice(fileName string, dataSlice []string) {
	obj, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
	if err != nil {
		log.Printf("[ERR]  Open File Error: %s\r\n", err.Error())
		return
	}
	defer obj.Close()

	var builder strings.Builder
	for _, s := range dataSlice {
		builder.WriteString(s + "\r\n")
	}
	_, err = obj.WriteString(builder.String())
	if err != nil {
		log.Printf("[ERR]  Write File Error: %s\r\n", err.Error())
		return
	}
}
