package encoder

import (
	"bytes"
	"compress/gzip"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"io"
	"io/ioutil"
	"local/encoder/pipe"
	"local/encryptor"
	"log"
	"strconv"

	"github.com/golang/snappy"
	"github.com/klauspost/reedsolomon"
	"github.com/pierrec/lz4"
)

var pipeBuffSize = int64(aes.BlockSize + 2)

func SetPipeBuffSize(n int) {
	if n < 50 {
		n = 50
	}
	pipeBuffSize = int64(n)
}

type BOP3 interface {
	Wrap3(chan []byte) chan []byte
	Unwrap3(chan []byte) chan []byte
	BOP
}

type BOP2 interface {
	Wrap2(io.Reader) io.Reader
	Unwrap2(io.Reader) io.Reader
	BOP
}

type BOP interface {
	Wrap([]byte) []byte
	Unwrap([]byte) []byte
}

type Base64 struct{}
type Zipper struct{}
type snapper struct{}
type symCryptor struct{ encryptor.Encryptor }
type asymCryptor struct{ encryptor.Encryptor }
type Salter struct{ n int }
type LZ4 struct{}
type AES struct {
	Key    []byte
	keylen int
	block  cipher.Block
}
type PAR2 struct {
	chunks int
	pars   int
	enc    reedsolomon.Encoder
}

func (p *PAR2) fix() error {
	if p.chunks == 0 {
		p.chunks = 50
	}
	if p.pars == 0 {
		p.pars = 5
	}
	if p.enc == nil {
		e, err := reedsolomon.New(p.chunks, p.pars)
		p.enc = e
		return err
	}
	return nil
}
func (p *PAR2) Unwrap2(r io.Reader) io.Reader {
	b, _ := ioutil.ReadAll(r)
	b = p.Unwrap(b)
	return bytes.NewReader(b)
}
func (p *PAR2) Wrap2(r io.Reader) io.Reader {
	b, _ := ioutil.ReadAll(r)
	b = p.Wrap(b)
	return bytes.NewReader(b)
}
func (p *PAR2) Wrap(b []byte) []byte {
	if p.fix() != nil {
		return b
	}
	split, err := p.enc.Split(b)
	if err != nil {
		return b
	}
	if err := p.enc.Encode(split); err != nil {
		return b
	}
	if ok, err := p.enc.Verify(split); err != nil || !ok {
		return b
	}
	l := strconv.Itoa(len(b))
	l = string(byte(len(l))) + l
	b = []byte(l)
	for i := range split {
		b = append(b, split[i]...)
	}
	return b
}
func (p *PAR2) Unwrap(b []byte) []byte {
	if p.fix() != nil {
		return b
	}
	l := int(b[0])
	o := b[1 : l+1]
	c := b[l+1:]
	m, err := strconv.Atoi(string(o))
	if err != nil {
		return b
	}
	split := make([][]byte, p.chunks+p.pars)
	chunklen := len(c) / (p.chunks + p.pars)
	for i := range split {
		split[i] = c[i*chunklen : (i+1)*chunklen]
	}
	if err := p.enc.Reconstruct(split); err != nil {
		return b
	}
	d := []byte{}
	for i := 0; i < p.chunks; i++ {
		d = append(d, split[i]...)
	}
	return d[:m]
}

func (z *Zipper) Wrap3(b chan []byte) chan []byte {
	out := make(chan []byte)
	go func() {
		defer close(out)
		pipeR, pipeW := io.Pipe()
		go func() {
			zw := gzip.NewWriter(pipeW)
			defer pipeW.Close()
			defer zw.Close()
			for input := range b {
				if _, err := zw.Write(input); err != nil {
					panic(err)
				}
			}
		}()
		subbuff := make([]byte, pipeBuffSize)
		for {
			n, err := pipeR.Read(subbuff)
			if err != nil && err != io.EOF {
				panic(err)
			} else if n > 0 {
				out <- copyBSlice(subbuff[:n])
			}
			if err == io.EOF {
				break
			}
		}
	}()
	return out
}
func (z *Zipper) Unwrap3(b chan []byte) chan []byte {
	out := make(chan []byte)
	go func() {
		pipeR, pipeW := io.Pipe()
		defer close(out)
		go func() {
			defer pipeW.Close()
			for input := range b {
				if _, err := pipeW.Write(input); err != nil {
					panic(err)
				}
			}
		}()
		zr, err := gzip.NewReader(pipeR)
		if err != nil {
			panic(err)
		}
		buff := make([]byte, pipeBuffSize)
		for {
			n, err := zr.Read(buff)
			if err != nil && err != io.EOF {
				panic(err)
			}
			if n > 0 {
				out <- copyBSlice(buff[:n])
			}
			if err == io.EOF {
				break
			}
		}
	}()
	return out
}

func (z *Zipper) Wrap2(b io.Reader) io.Reader {
	pipe := pipe.New()
	pipe.SetCap(pipeBuffSize)
	zw := gzip.NewWriter(pipe)
	go func() {
		defer pipe.Close()
		defer zw.Close()
		for {
			m, err := io.CopyN(zw, b, pipeBuffSize)
			if err != nil && err != io.EOF {
				panic(err)
			}
			if m > 0 {
				zw.Flush()
			}
			if err == io.EOF {
				return
			}
		}
		/*
			internalBuffer := make([]byte, pipeBuffSize/2+bytes.MinRead)
			if _, err := io.CopyBuffer(zw, b, internalBuffer); err != nil {
				panic(err)
			}
		*/
	}()
	return pipe
}
func (z *Zipper) Unwrap2(b io.Reader) io.Reader {
	pipe := pipe.New()
	pipe.SetCap(pipeBuffSize)
	go func() {
		defer pipe.Close()
		zr, err := gzip.NewReader(b)
		if err != nil && err != io.EOF {
			panic(err)
		} else if err == nil {
			if _, err := io.Copy(pipe, zr); err != nil {
				panic(err)
			}
			defer zr.Close()
		}
	}()
	return pipe
}

func (z *Zipper) Wrap(b []byte) []byte {
	var buf bytes.Buffer
	zw := gzip.NewWriter(&buf)
	if _, err := zw.Write(b); err != nil {
		return b
	}
	zw.Close()
	return buf.Bytes()
}

func (z *Zipper) Unwrap(b []byte) []byte {
	buf := bytes.NewBuffer(b)
	zw, err := gzip.NewReader(buf)
	if err != nil {
		return b
	}
	decompressed, err := ioutil.ReadAll(zw)
	if err != nil {
		return b
	}
	return decompressed
}

func (s *Salter) Unwrap3(b chan []byte) chan []byte {
	out := make(chan []byte)
	go func() {
		defer close(out)
		first := <-b
		saltLen := int(first[0])
		for len(first) < saltLen+1 {
			first = append(first, <-b...)
		}
		first = first[saltLen+1:]
		out <- copyBSlice(first)
		for input := range b {
			out <- copyBSlice(input)
		}
	}()
	return out
}

func (s *Salter) Wrap3(b chan []byte) chan []byte {
	out := make(chan []byte)
	salt := s.makeSalt()
	go func() {
		defer close(out)
		out <- copyBSlice(salt)
		for input := range b {
			out <- copyBSlice(input)
		}
	}()
	return out
}

func (s *Salter) makeSalt() []byte {
	n := 21
	if s.n > 0 {
		n = s.n
	}
	salt := make([]byte, n)
	_, err := rand.Read(salt)
	if err != nil {
		return nil
	}
	return append([]byte{byte(len(salt))}, salt...)
}

func (s *Salter) Wrap2(b io.Reader) io.Reader {
	salt := s.makeSalt()

	pipe := pipe.New()
	pipe.SetCap(pipeBuffSize)
	go func() {
		defer pipe.Close()
		if _, err := pipe.Write(salt); err != nil {
			panic(err)
		}
		tmpbuff := make([]byte, pipeBuffSize)
		if _, err := io.CopyBuffer(pipe, b, tmpbuff); err != nil {
			panic(err)
		}
	}()
	return pipe
}

func (s *Salter) Unwrap2(b io.Reader) io.Reader {
	buffer := bytes.NewBuffer(nil)
	if _, err := io.CopyN(buffer, b, 1); err != nil {
		panic(err)
	}
	l := int(buffer.Bytes()[0])
	if _, err := io.CopyN(buffer, b, int64(l)); err != nil {
		panic(err)
	}
	pipe := pipe.New()
	pipe.SetCap(pipeBuffSize)
	go func() {
		defer pipe.Close()
		if _, err := io.Copy(pipe, b); err != nil && err != io.EOF {
			panic(err)
		}
	}()
	return pipe
}

func (s *Salter) Wrap(b []byte) []byte {
	salt := s.makeSalt()

	return append(salt, b...)
}

func (s *Salter) Unwrap(b []byte) []byte {
	if len(b) < 1 {
		return b
	}
	l := int(b[0])
	if l > len(b)+1 {
		return b
	}
	return b[1+l:]
}

func (sc *symCryptor) Wrap(b []byte) []byte {
	return []byte(sc.Encrypt(string(b)))
}

func (sc *symCryptor) Unwrap(b []byte) []byte {
	return []byte(sc.Decrypt(string(b)))
}

func (sc *asymCryptor) Wrap(b []byte) []byte {
	return []byte(sc.Encrypt(string(b)))
}

func (sc *asymCryptor) Unwrap(b []byte) []byte {
	return []byte(sc.Decrypt(string(b)))
}

func (s *snapper) Wrap(b []byte) []byte {
	return snappy.Encode(nil, b)
}

func (s *snapper) Unwrap(b []byte) []byte {
	d, err := snappy.Decode(nil, b)
	if err != nil {
		return b
	}
	return d
}

func (s *AES) Wrap3(b chan []byte) chan []byte {
	s.fix()
	out := make(chan []byte)

	go func() {
		defer close(out)

		// IV
		iv := make([]byte, aes.BlockSize)
		if n, err := io.ReadFull(rand.Reader, iv); err != nil || n != len(iv) {
			panic(err)
		}
		out <- copyBSlice(iv)

		// cipher
		ttl := int64(0)
		stream := cipher.NewCFBEncrypter(s.block, iv)
		for input := range b {
			ttl += int64(len(input))
			stream.XORKeyStream(input, input)
			if len(input) > 0 {
				out <- copyBSlice(input)
			}
		}

		// padding
		out <- copyBSlice(s.getPadding(ttl))
	}()

	return out
}

func (s *AES) Wrap2(b io.Reader) io.Reader {
	s.fix()
	pipe := pipe.New()
	pipe.SetCap(pipeBuffSize)

	go func() {
		defer pipe.Close()

		// IV
		iv := make([]byte, aes.BlockSize)
		if n, err := io.ReadFull(rand.Reader, iv); err != nil || n != len(iv) {
			panic(err)
		}
		pipe.Write(iv)

		// cipher
		ttl := int64(0)
		stream := cipher.NewCFBEncrypter(s.block, iv)
		buff := make([]byte, pipeBuffSize)
		for n, err := b.Read(buff); err == nil || err == io.EOF; n, err = b.Read(buff) {
			ttl += int64(n)
			stream.XORKeyStream(buff[:n], buff[:n])
			if m, err := pipe.Write(buff[:n]); err != nil || m != n {
				panic(err)
			}
			if err == io.EOF {
				break
			}
		}

		// padding
		pipe.Write(s.getPadding(ttl))
	}()

	return pipe
}

func (s *AES) Unwrap2(b io.Reader) io.Reader {
	s.fix()
	pipe := pipe.New()
	pipe.SetCap(pipeBuffSize)

	go func() {
		defer pipe.Close()

		// IV
		var iv []byte
		add := make([]byte, pipeBuffSize)
		for len(iv) < aes.BlockSize {
			n, err := b.Read(add)
			if err != nil && err != io.EOF {
				panic(err)
			}
			iv = append(iv, add[:n]...)
		}
		last := iv[aes.BlockSize:]
		iv = iv[:aes.BlockSize]

		// cipher
		var n int
		var err error
		stream := cipher.NewCFBDecrypter(s.block, iv)
		for n, err = b.Read(add); err == nil || err == io.EOF; n, err = b.Read(add) {
			last = append(last, add[:n]...)
			stream.XORKeyStream(last[:len(last)-s.needModZero()], last[:len(last)-s.needModZero()])
			pipe.Write(last[:len(last)-s.needModZero()])
			last = last[len(last)-s.needModZero():]
			if err == io.EOF {
				break
			}
		}
		if err != nil && err != io.EOF {
			panic(err)
		}
		// padding
		trim := int(last[len(last)-1])
		last = last[:len(last)-trim]
		stream.XORKeyStream(last, last)
		pipe.Write(last)
	}()

	return pipe
}

func (s *AES) Unwrap3(b chan []byte) chan []byte {
	s.fix()
	out := make(chan []byte)

	go func() {
		defer close(out)

		// IV
		var lastTwo [2][]byte
		var iv []byte
		for len(iv) < aes.BlockSize {
			add := <-b
			if len(add) >= aes.BlockSize-len(iv) {
				isIV := aes.BlockSize - len(iv)
				lastTwo[0] = add[isIV:]
				add = add[:isIV]
			}
			iv = append(iv, add...)
		}

		// cipher
		stream := cipher.NewCFBDecrypter(s.block, iv)
		for input := range b {
			stream.XORKeyStream(lastTwo[1], lastTwo[1])
			if len(lastTwo[1]) > 0 {
				out <- copyBSlice(lastTwo[1])
			}
			lastTwo[1] = lastTwo[0]
			lastTwo[0] = input
		}
		last := append(lastTwo[1], lastTwo[0]...)
		// padding
		trim := int(last[len(last)-1])
		last = last[:len(last)-trim]
		stream.XORKeyStream(last, last)
		if len(last) > 0 {
			out <- copyBSlice(last)
		}
	}()

	return out
}

func (s *AES) Wrap(b []byte) []byte {
	s.fix()

	c := s.pad(b)

	d := make([]byte, aes.BlockSize+len(c))
	iv := d[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		return nil
	}

	stream := cipher.NewCFBEncrypter(s.block, iv)
	stream.XORKeyStream(d[aes.BlockSize:], c)

	return d
}

func (s *AES) Unwrap(b []byte) []byte {
	s.fix()

	iv := b[:aes.BlockSize]
	b = b[aes.BlockSize:]

	stream := cipher.NewCFBDecrypter(s.block, iv)
	stream.XORKeyStream(b, b)

	return s.unpad(b)
}
func (s *AES) fix() {
	if s.keylen < 1 {
		s.keylen = 32
	}
	if len(s.Key) != s.keylen {
		s.Key = bytes.Repeat(append(s.Key, 0), s.keylen)[:s.keylen]
	}
	if s.block == nil {
		s.block, _ = aes.NewCipher(s.Key)
	}
}

func (s *AES) unpad(b []byte) []byte {
	s.fix()
	if len(b) < 1 || len(b) < int(b[len(b)-1]) {
		return b
	}
	return b[:len(b)-int(b[len(b)-1])]
}

func (s *AES) pad(b []byte) []byte {
	s.fix()
	return append(b, s.getPadding(int64(len(b)))...)
}

func (s *AES) getPadding(l int64) []byte {
	padding := int(int64(s.needModZero()) - l%int64(s.needModZero()))
	return bytes.Repeat([]byte{byte(padding)}, padding)
}

func (s *AES) needModZero() int {
	return aes.BlockSize
}

func (l *LZ4) Wrap(b []byte) []byte {
	var buf bytes.Buffer
	w := lz4.NewWriter(&buf)
	if _, err := io.Copy(w, bytes.NewBuffer(b)); err != nil {
		panic(err)
	}
	if err := w.Close(); err != nil {
		panic(err)
	}
	return buf.Bytes()
}

func (l *LZ4) Unwrap(b []byte) []byte {
	zr := lz4.NewReader(bytes.NewBuffer(b))
	b, err := ioutil.ReadAll(zr)
	if err != nil {
		panic(err)
	}
	return b
}

func copyBSlice(b []byte) []byte {
	return append([]byte{}, b...)
}

func screenReader(b io.Reader) io.Reader {
	return b
	c, err := ioutil.ReadAll(b)
	if err != nil {
		panic(err)
	}
	log.Printf("screened %v", len(c))
	if len(c) > 20 {
		log.Printf("%s...%s", c[:5], c[len(c)-5:])
	} else if len(c) > 0 {
		log.Printf("%s", c)
	}
	return bytes.NewReader(c)
}

func (b64 *Base64) Wrap(b []byte) []byte {
	return []byte(base64.StdEncoding.EncodeToString(b))
}
func (b64 *Base64) Unwrap(b []byte) []byte {
	b, err := base64.StdEncoding.DecodeString(string(b))
	if err != nil {
		return nil
	}
	return b
}
