// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package libkb

import (
	"fmt"
	"runtime/debug"

	keybase1 "github.com/keybase/client/go/protocol/keybase1"
	triplesec "github.com/keybase/go-triplesec"
)

func NewSecureTriplesec(passphrase []byte, salt []byte) (Triplesec, error) {
	return triplesec.NewCipher(passphrase, salt, ClientTriplesecVersion)
}

func StretchPassphrase(g *GlobalContext, passphrase string, salt []byte) (tsec Triplesec, pps *PassphraseStream, err error) {
	if salt == nil {
		err = fmt.Errorf("no salt provided to StretchPassphrase")
		return nil, nil, err
	}
	var tmp []byte
	var fn func(pw []byte, salt []byte) (Triplesec, error)

	// free memory on mobile before we do this to reduce chance that we get killed because of the
	// large scrypt allocation coming
	if g != nil && g.IsMobileAppType() {
		debug.FreeOSMemory()
	}
	if g == nil {
		fn = NewSecureTriplesec
	} else {
		fn = g.NewTriplesec
	}

	tsec, err = fn([]byte(passphrase), salt)
	if err != nil {
		return nil, nil, err
	}
	_, tmp, err = tsec.DeriveKey(extraLen)
	if err != nil {
		return nil, nil, err
	}
	pps = NewPassphraseStream(tmp)
	return tsec, pps, nil
}

const (
	pwhIndex   = 0
	pwhLen     = 32
	eddsaIndex = pwhIndex + pwhLen
	eddsaLen   = 32
	dhIndex    = eddsaIndex + eddsaLen
	dhLen      = 32
	lksIndex   = dhIndex + dhLen
	lksLen     = LKSecLen // == 32
	extraLen   = pwhLen + eddsaLen + dhLen + lksLen
)

type PassphraseStream struct {
	stream []byte
	gen    PassphraseGeneration
}

func NewPassphraseStream(s []byte) *PassphraseStream {
	return &PassphraseStream{
		stream: s,
		gen:    PassphraseGeneration(0),
	}
}

// NewPassphraseStreamLKSecOnly creates a PassphraseStream only with the lks bytes
// (stream[lksIndex:]).  The rest of the stream is zeros.
// This is used to create a passphrase stream from the information in the
// secret store, which only contains the lksec portion of the stream.
func NewPassphraseStreamLKSecOnly(s *LKSec) (*PassphraseStream, error) {

	clientHalf, err := s.ComputeClientHalf()
	if err != nil {
		return nil, err
	}
	stream := make([]byte, extraLen)
	copy(stream[lksIndex:], clientHalf.Bytes())
	ps := &PassphraseStream{
		stream: stream,
		gen:    s.Generation(),
	}
	return ps, nil
}

func (ps *PassphraseStream) SetGeneration(gen PassphraseGeneration) {
	ps.gen = gen
}

type passphraseStreamPWHash [pwhLen]byte
type passphraseSteramEdDSASeed [eddsaLen]byte

func newPassphraseStreamFromPwhAndEddsa(pwhash passphraseStreamPWHash, eddsa passphraseSteramEdDSASeed) *PassphraseStream {
	stream := make([]byte, extraLen)
	copy(stream[pwhIndex:eddsaIndex], pwhash[:])
	copy(stream[eddsaIndex:dhIndex], eddsa[:])
	ps := &PassphraseStream{
		stream: stream,
		gen:    PassphraseGeneration(0),
	}
	return ps
}

func (ps PassphraseStream) PWHash() []byte {
	return ps.stream[pwhIndex:eddsaIndex]
}

func (ps PassphraseStream) EdDSASeed() []byte {
	return ps.stream[eddsaIndex:dhIndex]
}

func (ps PassphraseStream) DHSeed() []byte {
	return ps.stream[dhIndex:lksIndex]
}

func (ps PassphraseStream) LksClientHalf() LKSecClientHalf {
	ret, _ := NewLKSecClientHalfFromBytes(ps.stream[lksIndex:])
	return ret
}

func (ps PassphraseStream) ToLKSec(uid keybase1.UID) (*LKSec, error) {
	ch, err := NewLKSecClientHalfFromBytes(ps.stream[lksIndex:])
	if err != nil {
		return nil, err
	}
	return &LKSec{
		clientHalf: ch,
		ppGen:      ps.Generation(),
		uid:        uid,
	}, nil
}

func (ps PassphraseStream) PDPKA5KID() (keybase1.KID, error) {
	return seedToPDPKAKID(ps.EdDSASeed())
}

func (ps PassphraseStream) String() string {
	return fmt.Sprintf("pwh:   %x\nEdDSA: %x\nDH:    %x\nlks:   %x",
		ps.PWHash(), ps.EdDSASeed(), ps.DHSeed(), ps.LksClientHalf().Bytes())
}

// Generation returns the generation of this passphrase stream.
// It is >=0 for valid generation #.  If 0, then we assume the
// passphrase has never been reset.
func (ps PassphraseStream) Generation() PassphraseGeneration {
	return ps.gen
}

// Clone a passphrase stream and return a copy.
func (ps *PassphraseStream) Clone() *PassphraseStream {
	if ps == nil {
		return nil
	}
	arr := make([]byte, len(ps.stream))
	copy(arr, ps.stream)
	return &PassphraseStream{
		stream: arr,
		gen:    ps.gen,
	}
}

func (ps PassphraseStream) Export() keybase1.PassphraseStream {
	return keybase1.PassphraseStream{
		PassphraseStream: ps.stream,
		Generation:       int(ps.gen),
	}
}

func (ps PassphraseStream) SyncAndCheckIfOutdated(mctx MetaContext) (bool, error) {
	ss, err := mctx.SyncSecrets()
	if err != nil {
		return false, err
	}

	key, err := ss.FindDevice(mctx.G().Env.GetDeviceID())
	if err != nil {
		return false, err
	}

	return key.PPGen > ps.Generation(), nil
}
