/*
 MyStorageServer
   Storage Service
*/
package main

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"./Storage"
	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
)

const (
	USER_SERVICE      = "/user/"
	STORAGE_SERVICE   = "/storage/"
	STORAGE_DATA_BASE = "./Storage/DATA"
	HOST              = "localhost"
	PORT              = 3000
)

var (
	host     = HOST
	port     = PORT
	data_dir = STORAGE_DATA_BASE

	overwrite  = true
	debug_mode = false
)

func requestHandler(c echo.Context) error {
	return c.String(http.StatusOK, "My Storage Server Ver0.2")
}

/*
 ストレージサーバーに保存されているファイル一覧を取得するI/F
*/
func listFiles(c echo.Context) error {
	if debug_mode {
		fmt.Println("list files")
	}

	sr := Storage.NewStorageResource(data_dir)

	list := sr.FileList()

	if debug_mode {
		fmt.Println(list)
	}

	res := map[string][]string{
		"files": list,
	}

	return c.JSON(http.StatusOK, res)
}

/*
 URLからファイル名を組み立てる
*/
func filePath(file_name string) (string, string, error) {
	if debug_mode {
		fmt.Println("file name = " + file_name)
	}
	
	file := strings.Replace(file_name, STORAGE_SERVICE, "", 1)
	if strings.HasSuffix(file, "/") {
		// ファイル名が"/"で終わっていたらエラー
		return data_dir, "", echo.NewHTTPError(http.StatusBadRequest, "Invalid file name.")
	}
	if strings.Contains(file, "..") {
		// ファイル名に".."が存在すればエラー
		return data_dir, "", echo.NewHTTPError(http.StatusBadRequest, "Invalid file name.")
	}
	file = path.Clean(file)
	if file == "." {
		// ファイル名指定がないのでエラー
		return data_dir, "", echo.NewHTTPError(http.StatusBadRequest, "No file name specified.")

	}
	return data_dir + "/" + file, file, nil
}

/*
 ファイルを保存する
*/
func saveFile(rf io.Reader, file_path string) error {

	// ディレクトリ作成
	dir := path.Dir(file_path)
	err := os.MkdirAll(dir, 0775)
	if err != nil {
		return err
	}

	// ファイル作成
	wf, err := os.Create(file_path)
	if err != nil {
		return err
	}
	defer wf.Close()

	// Copy
	_, err = io.Copy(wf, rf)
	if err != nil {
		return err
	}

	return nil
}

/*
 ストレージサーバーにファイルを保存するI/F
 既存ファイルは上書きされる
*/
func createFile(c echo.Context) error {
	if debug_mode {
		fmt.Println("create file")
	}

	// URLからファイル名を組み立てる
	name := c.FormValue("name")
	file_path, file_name, err := filePath(name)
	if err != nil {
		return err
	}

	if overwrite == false {
		_, err := os.Stat(file_path)
		if os.IsExist(err) {
			return echo.NewHTTPError(http.StatusConflict, file_name+" is already exist.")
		}
	}

	// formから保存対象のファイルをオープンする
	file, err := c.FormFile("file")
	if err != nil {
		return err
	}
	rf, err := file.Open()
	if err != nil {
		return err
	}
	defer rf.Close()

	// ストレージにファイルを保存する
	err = saveFile(rf, file_path)
	if err != nil {
		return err
	}

	return c.NoContent(http.StatusOK)
}

/*
 ストレージサーバーにファイルを保存するI/F
 既存ファイルは上書きされる
*/
func updateFile(c echo.Context) error {
	if debug_mode {
		fmt.Println("update file")
	}

	// URLからファイル名を組み立てる
	file_name := c.Request().URL.Path
	file_path, _, err := filePath(file_name)
	if err != nil {
		return err
	}

	// リクエストボディからデータを読み込む
	rf := c.Request().Body

	// ストレージにファイルを保存する
	err = saveFile(rf, file_path)
	if err != nil {
		return err
	}

	return c.NoContent(http.StatusOK)
}

/*
 ストレージサーバーに保存されているファイルを読み出すI/F
*/
func readFile(c echo.Context) error {
	if debug_mode {
		fmt.Println("read file")
	}

	// URLからファイル名を組み立てる
	file_name := c.Request().URL.Path
	file_path, _, err := filePath(file_name)
	if err != nil {
		return err
	}

	// 存在チェック
	file_info, err := os.Stat(file_path)
	if os.IsNotExist(err) {
		return echo.NewHTTPError(http.StatusNotFound, file_name+" is not found.")
	}

	if debug_mode {
		size := file_info.Size()
		fmt.Printf("size of \"%s\" is %d\n", file_path, size)
	}

	// ファイルデータを送信
	return c.File(file_path)
}

/*
 ストレージサーバーに保存されているファイルの属性情報を取得するI/F
 取得できる情報は、サイズと更新日時
*/
func infoFile(c echo.Context) error {
	if debug_mode {
		fmt.Println("info file")
	}

	// URLからファイル名を組み立てる
	file_name := c.Request().URL.Path
	file_path, _, err := filePath(file_name)
	if err != nil {
		return err
	}

	// 存在チェック
	file_info, err := os.Stat(file_path)
	if os.IsNotExist(err) {
		return echo.ErrNotFound
	}

	if debug_mode {
		fmt.Println(file_name)
		fmt.Println(file_info.Size())
		fmt.Println(file_info.ModTime())
	}

	c.Response().Header().Set(echo.HeaderLastModified, file_info.ModTime().Format(time.RFC1123))
	c.Response().Header().Set(echo.HeaderContentLength, strconv.FormatInt(file_info.Size(), 10))
	return c.NoContent(http.StatusOK)
}

/*
 ストレージサーバーに保存されているファイルを削除するI/F
*/
func deleteFile(c echo.Context) error {
	if debug_mode {
		fmt.Println("delete file")
	}

	// URLからファイル名を組み立てる
	file_name := c.Request().URL.Path
	file_path, _, err := filePath(file_name)
	if err != nil {
		return err
	}

	err = os.Remove(file_path)
	if os.IsNotExist(err) {
		return echo.ErrNotFound
	}
	if err != nil {
		return err
	}

	// 空になったらディレクトリも消す
	d := filepath.Dir(file_path)
	for {
		if d == data_dir {
			break
		}
		if debug_mode {
			fmt.Println("remove " + d)
		}
		err = os.Remove(d)
		if err != nil {
			if debug_mode {
				fmt.Println("failed to remove " + d)
			}
			break
		}

		d = filepath.Dir(d)
	}

	return c.NoContent(http.StatusOK)
}

/*
 Config構造体
*/
type Config struct {
	Host       string `json:"host"`
	Port       int    `json:"port"`
	Data_store string `json:"data_store"`
}

/*
 JSON形式のコンフィギュレーションファイルを読み込み、Config構造体に入る
*/
func config(file string) (*Config, error) {
	confi_file, err := ioutil.ReadFile("config.json")
	if err != nil {
		fmt.Println("Config file read error.")
		return nil, err
	}
	var conf Config
	err = json.Unmarshal(confi_file, &conf)
	if debug_mode {
		fmt.Println(conf)
	}
	return &conf, err
}

/*
 ストレージサーバーのメイン処理
*/
func main() {

	fmt.Println("starting My Storage Server")

	// 設定ファイル読み込み
	conf, err := config("config.json")
	if err != nil {
		fmt.Println("Config file read error.")
		return
	}
	if conf.Host != "" {
		host = conf.Host
	}
	if conf.Port != 0 {
		port = conf.Port
	}
	if conf.Data_store != "" {
		data_dir = path.Clean(conf.Data_store)
	}

	if debug_mode {
		fmt.Println("host = " + host)
		fmt.Println("port = ", port)
		fmt.Println("data_dir = " + data_dir)
	}

	// Echoセットアップ
	e := echo.New()

	// アクセスログの設定
	e.Use(middleware.Logger())

	// エラー発生時の対処設定
	e.Use(middleware.Recover())

	// ストレージサービスのリクエストハンドラ登録
	e.GET("/", requestHandler)
	e.GET(STORAGE_SERVICE, listFiles)
	e.POST(STORAGE_SERVICE, createFile)
	e.PUT(STORAGE_SERVICE+"*", updateFile)
	e.GET(STORAGE_SERVICE+"*", readFile)
	e.HEAD(STORAGE_SERVICE+"*", infoFile)
	e.DELETE(STORAGE_SERVICE+"*", deleteFile)

	// サーバーの起動
	e.Logger.Fatal(e.Start(host + ":" + strconv.FormatInt(int64(port), 10)))

}
