package hotime

import (
	"bytes"
	"database/sql"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

type Application struct {
	MethodRouter
	Router
	contextBase
	Port            string                     //端口号
	TLSPort         string                     //ssl访问端口号
	connectListener []func(this *Context) bool //所有的访问监听,true按原计划继续使用，false表示有监听器处理
	connectDbFunc   func(err ...*Error) *sql.DB
	configPath      string
	Config          Map
	Db              HoTimeDB
	Server          *http.Server
	CacheIns
	sessionLong  CacheIns
	sessionShort CacheIns
	http.Handler
}

func (this *Application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	this.handler(w, req)
}

//启动实例
func (this *Application) Run(router Router) {
	//如果没有设置配置自动生成配置
	if this.configPath == "" || len(this.Config) == 0 {
		this.SetConfig()
	}

	//防止手动设置缓存误伤
	if this.CacheIns == nil {
		this.SetCache(CacheIns(&CacheMemory{}))
	}

	//防止手动设置session误伤
	if this.sessionShort == nil && this.sessionLong == nil {
		if this.connectDbFunc == nil {
			this.SetSession(CacheIns(&CacheMemory{}), nil)
		} else {
			this.SetSession(CacheIns(&CacheMemory{}), CacheIns(&CacheDb{Db: &this.Db, Time: this.Config.GetInt64("cacheLongTime")}))
		}

	}

	this.Router = router
	//重新设置MethodRouter//直达路由
	this.MethodRouter = MethodRouter{}
	modeRouterStrict := true
	if this.Config.Get("modeRouterStrict").(bool) == false {
		modeRouterStrict = false
	}
	if router != nil {
		for pk, pv := range router {
			if !modeRouterStrict {
				pk = strings.ToLower(pk)
			}
			if pv != nil {
				for ck, cv := range pv {
					if !modeRouterStrict {
						ck = strings.ToLower(ck)
					}
					if cv != nil {
						for mk, mv := range cv {
							if !modeRouterStrict {
								mk = strings.ToLower(mk)
							}
							this.MethodRouter["/"+pk+"/"+ck+"/"+mk] = mv
						}
					}
				}
			}
		}
	}

	//this.Port = port
	this.Port = this.Config.GetString("port")
	this.TLSPort = this.Config.GetString("tlsPort")

	if this.connectDbFunc != nil && (this.Db.DB == nil || this.Db.DB.Ping() != nil) {
		this.Db.SetConnect(this.connectDbFunc)
	}

	if this.CacheIns == nil {
		this.CacheIns = CacheIns(&CacheMemory{Map: Map{}, Time: this.Config.GetInt64("cacheShortTime")})
	}

	//异常处理
	defer func() {
		if err := recover(); err != nil {
			//this.SetError(errors.New(fmt.Sprint(err)), LOG_FMT)
			logFmt(err, 2, LOG_ERROR)

			this.Run(router)
		}
	}()

	this.Server = &http.Server{}
	if !IsRun {
		IsRun = true
	}

	ch := make(chan int)
	if ObjToCeilInt(this.Port) != 0 {
		go func() {

			App[this.Port] = this
			this.Server.Handler = this
			//启动服务
			this.Server.Addr = ":" + this.Port
			err := this.Server.ListenAndServe()
			logFmt(err, 2)
			ch <- 1

		}()
	} else if ObjToCeilInt(this.TLSPort) != 0 {
		go func() {

			App[this.TLSPort] = this
			this.Server.Handler = this
			//启动服务
			this.Server.Addr = ":" + this.TLSPort
			err := this.Server.ListenAndServeTLS(this.Config.GetString("tlsCert"), this.Config.GetString("tlsKey"))
			logFmt(err, 2)
			ch <- 2

		}()
	} else {
		logFmt("没有端口启用", 2, LOG_INFO)
		return

	}

	value := <-ch

	logFmt("启动服务失败 : "+ObjToStr(value), 2, LOG_ERROR)
}

//启动实例
func (this *Application) SetConnectDB(connect func(err ...*Error) *sql.DB) {

	//this.Db.DBCached=false
	//if this.Config.GetCeilInt("dbCached")!=0{
	//	this.Db.DBCached=true
	//}

	this.connectDbFunc = connect
	this.Db.SetConnect(this.connectDbFunc)

	this.Db.DBCached = false
	if this.Config.GetCeilInt("dbCached") != 0 {
		this.Db.DBCached = true
	}

	this.Db.Type = this.Config.GetString("dbType")

}

//设置配置文件路径全路径或者相对路径
func (this *Application) SetSession(short CacheIns, Long CacheIns) {
	this.sessionLong = Long
	this.sessionShort = short

}

//默认配置缓存和session实现
func (this *Application) SetDefault(connect func(err ...*Error) *sql.DB) {
	this.SetConfig()

	if connect != nil {
		this.connectDbFunc = connect
		this.Db.SetConnect(this.connectDbFunc)
	}

}

//设置配置文件路径全路径或者相对路径
func (this *Application) SetCache(cache CacheIns) {

	this.CacheIns = cache

}

//设置配置文件路径全路径或者相对路径
func (this *Application) SetConfig(configPath ...string) {
	if len(configPath) != 0 {
		this.configPath = configPath[0]
	}

	if this.configPath == "" {
		this.configPath = "config/config.json"
	}
	//加载配置文件
	btes, err := ioutil.ReadFile(this.configPath)
	this.Config = DeepCopyMap(Config).(Map)
	if err == nil {

		cmap := Map{}
		//文件是否损坏
		cmap.JsonToMap(string(btes), &this.Error)

		for k, v := range cmap {
			this.Config[k] = v //程序配置
			Config[k] = v      //系统配置
		}
	} else {
		logFmt("配置文件不存在，或者配置出错，使用缺省默认配置", 2)

	}

	//文件如果损坏则不写入配置防止配置文件数据丢失
	if this.Error.GetError() == nil {
		var configByte bytes.Buffer

		err = json.Indent(&configByte, []byte(this.Config.ToJsonString()), "", "\t")
		//判断配置文件是否序列有变化有则修改配置，五则不变
		//fmt.Println(len(btes))
		if len(btes) != 0 && configByte.String() == string(btes) {
			return
		}
		//写入配置说明
		var configNoteByte bytes.Buffer
		json.Indent(&configNoteByte, []byte(ConfigNote.ToJsonString()), "", "\t")

		os.MkdirAll(filepath.Dir(this.configPath), os.ModeDir)
		err = ioutil.WriteFile(this.configPath, configByte.Bytes(), os.ModeAppend)
		if err != nil {
			this.Error.SetError(err)
		}
		ioutil.WriteFile(filepath.Dir(this.configPath)+"/confignote.json", configNoteByte.Bytes(), os.ModeAppend)

	}

}

//连接判断,返回true继续传输至控制层，false则停止传输
func (this *Application) SetConnectListener(lis func(this *Context) bool) {
	this.connectListener = append(this.connectListener, lis)
}

//网络错误
//func (this *Application) session(w http.ResponseWriter, req *http.Request) {
//
//}

//序列化链接
func (this *Application) urlSer(url string) (string, []string) {
	q := strings.Index(url, "?")
	if q == -1 {
		q = len(url)
	}
	o := Substr(url, 0, q)

	r := strings.SplitN(o, "/", -1)

	var s = make([]string, 0)

	for i := 0; i < len(r); i++ {
		if !strings.EqualFold("", r[i]) {
			s = append(s, r[i])
		}
	}
	return o, s
}

//访问

func (this *Application) handler(w http.ResponseWriter, req *http.Request) {

	_, s := this.urlSer(req.RequestURI)
	//获取cookie
	// 如果cookie存在直接将sessionId赋值为cookie.Value
	// 如果cookie不存在就查找传入的参数中是否有token
	// 如果token不存在就生成随机的sessionId
	// 如果token存在就判断token是否在Session中有保存
	// 如果有取出token并复制给cookie
	// 没有保存就生成随机的session
	cookie, err := req.Cookie(this.Config.GetString("sessionName"))
	sessionId := Md5(strconv.Itoa(Rand(10)))
	token := req.FormValue("token")
	//isFirst:=false
	if err != nil || (len(token) == 32 && cookie.Value != token) {
		if len(token) == 32 {
			sessionId = token
		}
		//else{
		//	isFirst=true;
		//}
		http.SetCookie(w, &http.Cookie{Name: this.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
	} else {
		sessionId = cookie.Value
	}

	unescapeUrl, err := url.QueryUnescape(req.RequestURI)
	if err != nil {
		unescapeUrl = req.RequestURI
	}
	//访问实例
	context := Context{SessionIns: SessionIns{SessionId: sessionId,
		LongCache:  this.sessionLong,
		ShortCache: this.sessionShort,
	},
		CacheIns: this.CacheIns,
		Resp:     w, Req: req, Application: this, RouterString: s, Config: this.Config, Db: &this.Db, HandlerStr: unescapeUrl}
	//header默认设置
	header := w.Header()
	header.Set("Content-Type", "text/html; charset=utf-8")

	//url去掉参数并序列化
	context.HandlerStr, context.RouterString = this.urlSer(context.HandlerStr)

	//跨域设置
	this.crossDomain(&context)
	//是否展示日志
	if this.Config.GetInt("connectLogShow") != 0 {
		logFmt(Substr(context.Req.RemoteAddr, 0, strings.Index(context.Req.RemoteAddr, ":"))+" "+context.HandlerStr, 0, LOG_INFO)
	}

	//访问拦截true继续false暂停
	connectListenerLen := len(this.connectListener)
	if connectListenerLen != 0 {
		for i := 0; i < connectListenerLen; i++ {

			if !this.connectListener[i](&context) {

				context.View()
				return
			}
		}
	}

	//接口服务
	//if len(s) == 3 {
	//	//如果满足规则则路由到对应控制器去
	//	if this.Router[s[0]] != nil && this.Router[s[0]][s[1]] != nil && this.Router[s[0]][s[1]][s[2]] != nil {
	//		//控制层
	//		this.Router[s[0]][s[1]][s[2]](&context)
	//		//header.Set("Content-Type", "text/html; charset=utf-8")
	//		context.View()
	//		return
	//	}
	//
	//}
	//验证接口严格模式
	modeRouterStrict := this.Config.Get("modeRouterStrict").(bool)
	tempHandlerStr := context.HandlerStr
	if !modeRouterStrict {
		tempHandlerStr = strings.ToLower(tempHandlerStr)
	}

	//执行接口
	if this.MethodRouter[tempHandlerStr] != nil {
		this.MethodRouter[tempHandlerStr](&context)
		context.View()
		return
	}

	//url赋值
	path := this.Config.GetString("tpt") + tempHandlerStr

	//判断是否为默认
	if path[len(path)-1] == '/' {
		defFile := this.Config.GetSlice("defFile")

		for i := 0; i < len(defFile); i++ {
			temp := path + defFile.GetString(i)
			_, err := os.Stat(temp)

			if err == nil {
				path = temp
				break
			}
		}
		if path[len(path)-1] == '/' {
			w.WriteHeader(404)
			return
		}
	}

	if strings.Contains(path, "/.") {
		w.WriteHeader(404)
		return
	}

	//设置header
	delete(header, "Content-Type")
	if this.Config.GetInt("debug") != 1 {
		header.Set("Cache-Control", "public")
	}

	if strings.Index(path, ".m3u8") != -1 {
		header.Add("Content-Type", "audio/mpegurl")
	}

	//w.Write(data)
	http.ServeFile(w, req, path)

}

func (this *Application) crossDomain(context *Context) {
	//没有跨域设置
	if context.Config.GetString("crossDomain") == "" {
		return
	}

	header := context.Resp.Header()
	header.Set("Access-Control-Allow-Origin", "*")
	header.Set("Access-Control-Allow-Methods", "*")
	header.Set("Access-Control-Allow-Credentials", "true")
	header.Set("Access-Control-Expose-Headers", "*")
	header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token")

	if context.Config.GetString("crossDomain") != "*" {
		header.Set("Access-Control-Allow-Origin", this.Config.GetString("crossDomain"))
		return
	}

	origin := context.Req.Header.Get("Origin")
	if origin != "" {
		header.Set("Access-Control-Allow-Origin", origin)
		return
	}

	refer := context.Req.Header.Get("Referer")
	if refer != "" {
		tempInt := 0
		lastInt := strings.IndexFunc(refer, func(r rune) bool {
			if r == '/' && tempInt > 8 {
				return true
			}
			tempInt++
			return false
		})

		if lastInt < 0 {
			lastInt = len(refer)
		}
		refer = Substr(refer, 0, lastInt)
		header.Set("Access-Control-Allow-Origin", refer)
	}
}
