package spider

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"

	//"log"
	"mime/multipart"
	"net/http"
	"net/http/cookiejar"

	//"net/http/httputil"
	"compress/gzip"

	//"filestore"
	"net/textproto"
	"net/url"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

const (
	Chrome  = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.97 Safari/537.36"
	Firefox = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:44.0) Gecko/20100101 Firefox/44.0"

	MAX_MEMORY = 1 * 1024 * 1024
)

var (
	defaultHeaders = map[string]string{
		"User-Agent":      Firefox,
		"Accept":          "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
		"Accept-Encoding": "deflate",
		"Accept-Language": "en-US,en;q=0.5",
		"Cache-Control":   "max-age=0",
		"Connection":      "keep-alive",
	}
)

var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")

func escapeQuotes(s string) string {
	return quoteEscaper.Replace(s)
}

func fileExt(url string) (ext string) {
	ext = filepath.Ext(url)
	if strings.HasPrefix(ext, ".") {
		ext = ext[1:]
	}
	ext = strings.ToLower(ext)
	return
}

type DownloadResult struct {
	Url    string
	Ext    string
	FileID string
	Error  error
}

type UploadFile struct {
	FieldName string
	FileName  string
	File      io.Reader
}

type Spider struct {
	client     *http.Client
	currentURL string
	headers    map[string]string
	userAgent  string
}

func NewSpider(headers map[string]string) (*Spider, error) {
	jar, err := cookiejar.New(nil)
	if err != nil {
		return nil, err
	}
	s := new(Spider)
	s.client = &http.Client{
		Jar:     jar,
		Timeout: time.Duration(60) * time.Second,
	}
	if headers == nil {
		s.headers = defaultHeaders
	} else {
		s.headers = headers
	}
	return s, nil
}

func (s *Spider) Cookies(u *url.URL) {
	for _, c := range s.client.Jar.Cookies(u) {
		fmt.Printf("%s\n%s\n%d\n%s\n\n", c.Name, c.Value, c.MaxAge, c.Expires)
	}
}

func (s *Spider) PushHeaders(headers map[string]string) {
	for k, v := range headers {
		s.headers[k] = v
	}
}

func (s *Spider) CurrentURL() string {
	return s.currentURL
}

func (s *Spider) SetUserAgent(userAgent string) {
	s.userAgent = userAgent
}

func (s *Spider) SetCookies(urlStr string, cookies []*http.Cookie) {
	u, _ := url.Parse(urlStr)
	s.client.Jar.SetCookies(u, cookies)
}

func (s *Spider) Get(urlStr string, headers map[string]string) ([]byte, error) {
	req, err := http.NewRequest("GET", urlStr, nil)
	if err != nil {
		return nil, err
	}
	// set default headers
	for k, v := range s.headers {
		req.Header.Set(strings.Title(k), v)
	}
	req.Header.Set("Referer", s.currentURL)
	// rewrite headers (can rewrite referer or user-agent)
	for k, v := range headers {
		req.Header.Set(strings.Title(k), v)
	}
	return s.send(req)
}

func (s *Spider) Post(urlStr string, data url.Values, headers map[string]string) ([]byte, error) {
	req, err := http.NewRequest("POST", urlStr, bytes.NewBufferString(data.Encode()))
	if err != nil {
		return nil, err
	}
	// set default headers
	for k, v := range s.headers {
		req.Header.Set(strings.Title(k), v)
	}
	req.Header.Set("Referer", s.currentURL)
	// rewrite headers (can rewrite referer or user-agent)
	for k, v := range headers {
		req.Header.Set(strings.Title(k), v)
	}

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	return s.send(req)
}

// data, files, headers - can set to "nil"
func (s *Spider) PostMultipart(urlStr string, data url.Values, files []UploadFile, headers map[string]string) ([]byte, error) {
	body := bytes.NewBuffer(nil)
	writer := multipart.NewWriter(body)
	writer.SetBoundary("-----------------------------211874649514344638761493651422")
	for k, v := range data {
		if err := writer.WriteField(k, v[0]); err != nil {
			return nil, err
		}
	}

	for _, f := range files {
		// own
		h := make(textproto.MIMEHeader)
		h.Set("Content-Disposition",
			fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
				escapeQuotes(f.FieldName), escapeQuotes(f.FileName)))
		h.Set("Content-Type", "text/plain")
		part, err := writer.CreatePart(h)
		if err != nil {
			return nil, err
		}

		if _, err = io.Copy(part, f.File); err != nil {
			return nil, err
		}
		part, err = writer.CreateFormFile("files[]", "")
		if err != nil {
			return nil, err
		}
	}
	contentType := writer.FormDataContentType()

	if err := writer.Close(); err != nil {
		return nil, err
	}

	req, err := http.NewRequest("POST", urlStr, body)
	if err != nil {
		return nil, err
	}
	// set default headers
	for k, v := range s.headers {
		req.Header.Set(strings.Title(k), v)
	}
	req.Header.Set("Referer", s.currentURL)
	// rewrite headers (can rewrite referer or user-agent)
	for k, v := range headers {
		req.Header.Set(strings.Title(k), v)
	}

	req.Header.Set("Content-Length", strconv.Itoa(body.Len())) // set content type and boundary
	req.Header.Set("Content-Type", contentType)                // set content type and boundary
	return s.send(req)
}

/*
func (s *Spider) DownloadFiles(urls []string, fs *filestore.FileStore) (results []*DownloadResult) {
	var (
		resp *http.Response
		//tmp  *filestore.TempFile
	)
	for _, url := range urls {
		result := &DownloadResult{
			Url: url,
		}
		results = append(results, result)

		if resp, result.Error = http.Get(url); result.Error != nil {
			continue
		}
		defer resp.Body.Close()

		if resp.StatusCode != 200 {
			result.Error = errors.New(resp.Status)
			continue
		}

		result.Ext = fileExt(url)
		if result.Ext == "" {
			// FIX добавить парсинг mime-типа
			//resp.Header.Get("Content-Type")
		}

		result.FileID, result.Error = fs.Put(resp.Body, result.Ext)
	}
	return
}
*/

func (s *Spider) send(req *http.Request) ([]byte, error) {
	if s.userAgent != "" {
		req.Header.Set("User-Agent", s.userAgent)
	}
	resp, err := s.client.Do(req)
	if err != nil {
		//dump, err := httputil.DumpRequestOut(req, true)
		//if err != nil {
		//	fmt.Println("dump:", err)
		//}
		//fmt.Printf("dump ok:\n\n%s\n\n", dump)

		return nil, err
	}
	defer resp.Body.Close()

	/*
		dump, err := httputil.DumpRequestOut(req, true)
		if err != nil {
			return nil, err
		}
		fmt.Printf("\n\n%s\n\n", dump)

		fmt.Println("Response Cookies:")

		for _, c := range resp.Cookies() {
			fmt.Println(c)
		}
	*/

	var reader io.Reader

	if "gzip" == resp.Header.Get("Content-Encoding") {
		reader, err = gzip.NewReader(resp.Body)
		if err != nil {
			return nil, err
		}
	} else {
		reader = resp.Body
	}

	buf, err := ioutil.ReadAll(reader)
	if err != nil {
		return nil, err
	}
	s.currentURL = resp.Request.URL.String()

	return buf, nil
}

func debug(data []byte, err error) {
	if err == nil {
		fmt.Printf("%s\n\n", data)
	} else {
		fmt.Printf("%s\n\n", err.Error())
	}
}

/*

TEST MULTIPART UPLOAD

func main() {
	http.HandleFunc("/", upload)
	log.Fatal(http.ListenAndServe(":7777", nil))
}

func upload(w http.ResponseWriter, r *http.Request) {
	fmt.Println("call\n")
	if r.Method == "POST" {
		if err := r.ParseMultipartForm(MAX_MEMORY); err != nil {
			http.Error(w, err.Error(), http.StatusForbidden)
		}
		fmt.Println("parsed\n")
		//fmt.Printf("%v\n", r.MultipartForm)
		for key, value := range r.MultipartForm.Value {
			fmt.Printf("%s: %s\n", key, value)
		}

		for _, fileHeaders := range r.MultipartForm.File {
			for _, fileHeader := range fileHeaders {
				file, _ := fileHeader.Open()
				fmt.Printf("filename %s\n", fileHeader.Filename)
				buf, _ := ioutil.ReadAll(file)
				fmt.Printf("%s\n", string(buf))
			}
		}
	}
	http.Error(w, "woohoo!", http.StatusOK)
}


func SendMultipart() {
    spider, _ := NewSpider(nil)

    data := url.Values{}
    data.Set("user", "dima")
    data.Set("pwd", "secret")

    f1 := bytes.NewBufferString("inside file one")
    //f2 := bytes.NewBufferString("inside file two")
    f2, _ := os.Open("spider_test.go")

    ufs := []UploadFile{
        UploadFile{
            FieldName: "f1",
            FileName:  "f1.txt",
            File:      f1,
        },
        UploadFile{
            FieldName: "f2",
            FileName:  "f2.txt",
            File:      f2,
        },
    }

    buf, err := spider.PostMultipart("http://localhost:7777", data, ufs, nil)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s\n", string(buf))
}
*/
