package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"os/signal"
	"strconv"
)

// 增加额外的handler让客户端可以创建，读取，更新和删除数据库记录。
// 例如，一个形如 /update?item=socks&price=6
// 的请求会更新库存清单里一个货品的价格并且当这个货品不存在或价格无效时返回一个错误值。
//（注意：这个修改会引入变量同时更新的问题）

// TODO:
// 1. 将数据保存到本地json文件
// 2. 开启web服务时读取本地json文件
// 3. 读取数据时，如果本地json文件不存在，则从默认值获取数据
// 4. 实现web服务器
// 5. 实现增删改查
// 6. 关闭服务器时保存数据到本地json文件
type dollars float32

func (d dollars) String() string {
	return fmt.Sprintf("$%.2f", d)
}

type database map[string]dollars

// list 货物清单
func (db database) list(w http.ResponseWriter, req *http.Request) {
	for item, price := range db {
		fmt.Fprintf(w, "%s: %s\n", item, price)
	}
}

// price 价格
func (db database) price(w http.ResponseWriter, req *http.Request) {
	item := req.URL.Query().Get("item")
	price, ok := db[item]
	if !ok {
		w.WriteHeader(http.StatusNotFound) // 404
		fmt.Fprintf(w, "no such item: %q\n", item)
		return
	}
	fmt.Fprintf(w, "%s\n", price)
}

// create 创建
func (db database) create(w http.ResponseWriter, req *http.Request) {
	item := req.URL.Query().Get("item")
	price := req.URL.Query().Get("price")
	_, ok := db[item]
	if ok {
		w.WriteHeader(http.StatusNotFound) // 404
		fmt.Fprintf(w, "item 已经存在: %q\n", item)
		return
	}
	// string to float
	priceF, err := strconv.ParseFloat(price, 32)
	if err != nil {
		w.WriteHeader(http.StatusNotFound) // 404
		fmt.Fprintf(w, "price is invalid: %q\n", price)
		return
	}
	db[item] = dollars(priceF)
	fmt.Fprintf(w, "create item: %q\n", item)
	fmt.Fprintf(os.Stdout, "item create: %q\n", db)
}

// update 更新
func (db database) update(w http.ResponseWriter, req *http.Request) {
	item := req.URL.Query().Get("item")
	price := req.URL.Query().Get("price")
	_, ok := db[item]
	if !ok {
		w.WriteHeader(http.StatusNotFound) // 404
		fmt.Fprintf(w, "item 不存在: %q\n", item)
		return
	}
	// string to float
	priceF, err := strconv.ParseFloat(price, 32)
	if err != nil {
		w.WriteHeader(http.StatusNotFound) // 404
		fmt.Fprintf(w, "price is invalid: %q\n", price)
		return
	}
	db[item] = dollars(priceF)
	fmt.Fprintf(w, "update item: %q\n", item)
	fmt.Fprintf(os.Stdout, "item update: %q\n", db)
}

// delete 删除
func (db database) delete(w http.ResponseWriter, req *http.Request) {
	item := req.URL.Query().Get("item")
	_, ok := db[item]
	if !ok {
		w.WriteHeader(http.StatusNotFound) // 404
		fmt.Fprintf(w, "item 不存在: %q\n", item)
		return
	}
	delete(db, item)
	fmt.Fprintf(w, "delete item: %q\n", item)
	fmt.Fprintf(os.Stdout, "item delete: %q\n", db)
}

// 默认数据
var defaultDB database = database{"shoes": 50, "socks": 5}

// 数据库
var db database

func loadDB() error {
	path := "/Users/dugulingping/code/Golang/Study/src/study/day9Interface/Practice/db.json"
	// 打开文件
	file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0755)
	if err != nil {
		return err
	}
	// 关闭文件
	defer file.Close()
	// 读取文件, 解析json文件
	decoder := json.NewDecoder(file)
	if err := decoder.Decode(&db); err != nil {
		// 如果文件为空，则从默认值获取数据
		if err == io.EOF {
			// 从默认值获取数据
			fmt.Fprintln(os.Stdout, "从默认值获取数据")
			db = defaultDB
			return nil
		} else {
			// 如果文件不为空，但是解析失败，则返回错误
			return err
		}
	}
	return nil
}

// saveDB 保存数据到本地json文件
func saveDB() {
	path := "/Users/dugulingping/code/Golang/Study/src/study/day9Interface/Practice/db.json"
	// 打开文件
	file, err := os.OpenFile(path, os.O_RDWR, 0755)
	if err != nil {
		panic(err)
	}
	// 关闭文件
	defer file.Close()
	// 清空文件
	file.Truncate(0)
	// 从头开始写入
	file.Seek(0, 0)
	// 编码json文件
	encoder := json.NewEncoder(file)
	if err := encoder.Encode(db); err != nil {
		panic(err)
	}
	fmt.Fprintf(os.Stdout, "save db: %q", db)
}
func main() {
	// 从本地json文件读取数据
	err := loadDB()
	if err != nil {
		fmt.Fprintf(os.Stdout, "loadDB err: %q", err)
	}
	// 创建通道
	c := make(chan os.Signal)
	// 监听指定信号 ctrl+c kill
	signal.Notify(c, os.Interrupt)
	// 启动一个协程
	// 目的是当接收到指定信号时，执行保存数据库
	go func() {
		select {
		case sig := <-c:
			fmt.Printf("Got %s signal. Aborting...\n", sig)
			// 保存数据库
			saveDB()
			// 退出程序
			os.Exit(1)
		}
	}()
	// 创建路由
	http.HandleFunc("/list", db.list)
	http.HandleFunc("/price", db.price)
	http.HandleFunc("/create", db.create)
	http.HandleFunc("/update", db.update)
	http.HandleFunc("/delete", db.delete)
	// 监听端口
	http.ListenAndServe("localhost:8000", nil)

}
