package main

import (
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"hash/crc32"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/akamensky/argparse"
)

func swab16(x uint16) uint16 {
	return (x&0x00ff)<<8 | (x&0xff00)>>8
}

func swab32(x uint32) uint32 {
	return (x&0x000000ff)<<24 | (x&0x0000ff00)<<8 | (x&0x00ff0000)>>8 | (x&0xff000000)>>24
}

func sumArr(arr []uint8) uint32 {
	sum := uint32(0)
	for _, val := range arr {
		sum += uint32(val)
	}
	return sum
}

func calChecknum(_type string, datas []byte) uint32 {
	if _type == "sum32" {
		return sumArr(datas)
	} else if _type == "crc32" {
		return crc32.ChecksumIEEE(datas)
	} else {
		print("unKown type: " + _type)
		return 0
	}
}

func getFileSize(filename string) (int64, error) {
	fileinfo, err := os.Stat(filename)
	if err != nil {
		return 0, err
	}
	size := fileinfo.Size()
	return size, nil
}

func ReadBinFile(_name string, _type string, _en string, _block int) {
	FileName := filepath.Base(_name)
	// FileType := filepath.Ext(_name)

	InputFilePath := _name
	OutputFilePath := FileName + "." + _type

	inputFile, err := os.Open(InputFilePath)
	if err != nil {
		panic(err)
	}
	defer inputFile.Close()

	outputFile, err := os.OpenFile(OutputFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		panic(err)
	}
	defer outputFile.Close()

	if _block > 0 {
		buffer := make([]byte, _block)
		for {
			n, err := inputFile.Read(buffer)
			if n > 0 {
				// 计算分片的校验和
				check := calChecknum(_type, buffer[:n])
				// 写入分片内容+分片校验和
				if _en == "big" {
					binary.Write(outputFile, binary.BigEndian, buffer[:n])
					binary.Write(outputFile, binary.BigEndian, check)
				} else {
					binary.Write(outputFile, binary.LittleEndian, buffer[:n])
					binary.Write(outputFile, binary.LittleEndian, check)
				}
			}

			if err != nil {
				if err == io.EOF {
					break // 文件读取完毕
				}
				panic(err)
			}
		}
	} else {
		data, err := ioutil.ReadAll(inputFile)
		if err != nil {
			panic(err)
		}
		// 写入全部内容
		if _en == "big" {
			binary.Write(outputFile, binary.BigEndian, data)
		} else {
			binary.Write(outputFile, binary.LittleEndian, data)
		}
	}

	// 读取文件内容到字节切片
	inputFile, err = os.Open(InputFilePath)
	if err != nil {
		panic(err)
	}
	defer inputFile.Close()
	data, err := ioutil.ReadAll(inputFile)
	if err != nil {
		panic(err)
	}

	check := calChecknum(_type, data[:])
	// 写入源文件校验码
	if _en == "big" {
		binary.Write(outputFile, binary.BigEndian, check)
	} else {
		binary.Write(outputFile, binary.LittleEndian, check)
	}

	// 大小端转换
	var networkBytes [4]byte
	if _en == "big" {
		binary.BigEndian.PutUint32(networkBytes[:], check)
	} else {
		binary.LittleEndian.PutUint32(networkBytes[:], check)
	}
	check_hex := hex.EncodeToString(networkBytes[:])

	// 打印输出内容
	size, err := getFileSize(InputFilePath)
	fmt.Println(InputFilePath, fmt.Sprintf("%d", size), _type, check_hex)

	// 关闭文件
	err = outputFile.Close()
	if err != nil {
		fmt.Println(err)
	}
	// 重命名文件
	out_name1 := FileName + "." + _type + "[" + check_hex + "]"
	err = os.Rename(OutputFilePath, out_name1)
	if err != nil {
		fmt.Println("重命名文件出错：", err)
		return
	}
}

func main() {
	parser := argparse.NewParser("goChecknum", "这是一个用于固件校验码工具, 制作升级文件")
	_name := parser.String("n", "name", &argparse.Options{Required: true, Help: "bin文件名, 譬如 test.bin", Default: "test.bin"})
	_type := parser.String("t", "type", &argparse.Options{Required: false, Help: "校验方式: sum32,crc32", Default: "sum32"})
	_en := parser.String("e", "en", &argparse.Options{Required: false, Help: "校验码大小端: little,big", Default: "little"})
	_section := parser.Int("s", "section", &argparse.Options{Required: false, Help: "分块大小: 0,1024,2048", Default: 0})
	err := parser.Parse(os.Args)
	if err != nil {
		fmt.Print(parser.Usage(err)) // 帮助 -h or --help
		return
	}
	ReadBinFile(*_name, *_type, *_en, *_section)
}
