package encoder

import (
	"errors"
	"io"
	"local/encryptor"
	"os"
	"path"
)

type Encoder struct {
	bops []BOP
}

func New(config *Config, bops ...BOP) (*Encoder, error) {
	if len(bops) != 0 {
		return &Encoder{bops: bops}, nil
	}

	bops = []BOP{
		&Zipper{},
		&Salter{},
	}

	if config != nil {
		if config.SymKey != "" {
			bops = append(bops, &AES{Key: []byte(config.SymKey)})
		}

		if !config.SymKeyOnly {
			var encr encryptor.Encryptor
			if path.Base(config.KeyPath) == "env" {
				pub := os.Getenv("PUB")
				pri := os.Getenv("PRI")
				if pub == "" || pri == "" {
					return nil, errors.New("PUB and PRI cannot be empty")
				}
				encr = encryptor.NewEncryptor(pri, pub, false)
			} else if config.KeyPath != "" {
				encr = encryptor.NewEncryptor("", "", false)
				priKeyPath := path.Join(config.KeyPath, "key.pri")
				pubKeyPath := path.Join(config.KeyPath, "key.pub")
				defer encr.ToFiles(priKeyPath, pubKeyPath)
				if err := encr.FromFiles(priKeyPath, pubKeyPath); err != nil {
					return nil, err
				}
			} else {
				encr = encryptor.NewEncryptor("", "", false)
			}
			bops = append(bops, &asymCryptor{Encryptor: encr})
		}
	}

	bops = append(bops, &Zipper{})
	return &Encoder{
		bops: bops,
	}, nil
}

func (e *Encoder) Encode2(r io.Reader) (io.Reader, error) {
	pipeR, pipeW := io.Pipe()
	go func() {
		defer pipeW.Close()
		for i := 0; i < len(e.bops); i++ {
			r = e.bops[i].(BOP2).Wrap2(r)
		}
		if _, err := io.Copy(pipeW, r); err != nil {
			panic(err)
		}
	}()
	return pipeR, nil
}

func (e *Encoder) Decode2(r io.Reader) (io.Reader, error) {
	pipeR, pipeW := io.Pipe()
	go func() {
		defer pipeW.Close()
		r = screenReader(r)
		for i := len(e.bops) - 1; i >= 0; i-- {
			r = e.bops[i].(BOP2).Unwrap2(r)
			r = screenReader(r)
		}
		if _, err := io.Copy(pipeW, r); err != nil {
			panic(err)
		}
	}()
	return pipeR, nil
}

func (e *Encoder) Encode3(r io.Reader) (io.Reader, error) {
	pipeR, pipeW := io.Pipe()

	bopCh := make([]chan []byte, len(e.bops)+1)
	inputChIndex := len(bopCh) - 1
	bopCh[inputChIndex] = make(chan []byte)
	//[0]=bops[0]([n+1])
	//[1]=bops[1]([0])
	//...
	//[n]=bops[n]([n-1])
	//[n+1]=firstin
	for i := range e.bops {
		bopCh[i] = e.bops[i].(BOP3).Wrap3(bopCh[(i-1+len(bopCh))%len(bopCh)])
	}
	go func() {
		defer pipeW.Close()
		for input := range bopCh[inputChIndex-1] {
			if _, err := pipeW.Write(input); err != nil {
				panic(err)
			}
		}
	}()
	go func() {
		defer close(bopCh[inputChIndex])
		buff := make([]byte, 64000)
		for {
			n, err := r.Read(buff)
			if err != nil && err != io.EOF {
				panic(err)
			}
			if n > 0 {
				bopCh[inputChIndex] <- copyBSlice(buff[:n])
			}
			if err == io.EOF {
				break
			}
		}
	}()
	return pipeR, nil
}

func (e *Encoder) Decode3(r io.Reader) (io.Reader, error) {
	pipeR, pipeW := io.Pipe()

	bopCh := make([]chan []byte, len(e.bops)+1)
	inputChIndex := len(bopCh) - 1
	bopCh[inputChIndex] = make(chan []byte)
	//[0]=bops[0]([1])
	//[1]=bops[1]([2])
	//...
	//[n]=bops[n]([n+1])
	//[n+1]=firstin
	for i := len(e.bops) - 1; i >= 0; i-- {
		bopCh[i] = e.bops[i].(BOP3).Unwrap3(bopCh[i+1])
	}
	go func() {
		defer pipeW.Close()
		for input := range bopCh[0] {
			if _, err := pipeW.Write(input); err != nil {
				panic(err)
			}
		}
	}()
	go func() {
		defer close(bopCh[inputChIndex])
		buff := make([]byte, 64000)
		for {
			n, err := r.Read(buff)
			if err != nil && err != io.EOF {
				panic(err)
			}
			if n > 0 {
				bopCh[inputChIndex] <- copyBSlice(buff[:n])
			}
			if err == io.EOF {
				break
			}
		}
	}()
	return pipeR, nil
}

func (e *Encoder) Encode(payload []byte, metadata ...[]byte) ([]byte, error) {
	if len(payload) == 0 {
		return nil, nil
	}

	for i := range e.bops {
		payload = e.bops[i].Wrap(payload)
		if len(payload) == 0 {
			return nil, errors.New("cannot encode payload")
		}
	}

	return e.WriteMetadata(payload, metadata)
}

func (e *Encoder) Decode(payload []byte) ([]byte, error) {
	metalen, err := e.metadataLength(payload)
	if err != nil {
		return nil, err
	}
	payload = payload[metalen:]

	if len(payload) == 0 {
		return nil, nil
	}

	for i := len(e.bops); i > 0; i-- {
		payload = e.bops[i-1].Unwrap(payload)
		if len(payload) == 0 {
			return nil, errors.New("cannot decode payload")
		}
	}

	return payload, nil
}

func (e *Encoder) WriteMetadata(payload []byte, metadata [][]byte) ([]byte, error) {
	meta := []byte{byte(len(metadata))}
	for i := range metadata {
		meta = append(meta, byte(len(metadata[i])))
		meta = append(meta, metadata[i]...)
	}
	payload = append(meta, payload...)
	return payload, nil
}

func (e *Encoder) ReadMetadata(payload []byte) ([][]byte, error) {
	metadata := [][]byte{}
	if len(payload) < 2 {
		return nil, nil
	}
	metalen := int(payload[0])
	payload = payload[1:]
	for i := 0; i < metalen; i++ {
		if len(payload) < 1 {
			return nil, nil
		}
		j := payload[0]
		if len(payload) < 1+int(j) {
			return nil, nil
		}
		metadata = append(metadata, payload[1:1+int(j)])
		payload = payload[1+int(j):]
	}
	return metadata, nil
}

func (e *Encoder) metadataLength(payload []byte) (int, error) {
	metadata, err := e.ReadMetadata(payload)
	if err != nil {
		return 0, err
	}
	n := 1
	for i := range metadata {
		n += 1 + len(metadata[i])
	}
	if len(payload) < n {
		return 0, nil
	}
	return n, nil
}
