//
//  tank
//
//  Import and Playlist Daemon for autoradio project
//
//
//  Copyright (C) 2017-2018 Christian Pointner <equinox@helsinki.at>
//
//  This file is part of tank.
//
//  tank is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  any later version.
//
//  tank is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with tank. If not, see <http://www.gnu.org/licenses/>.
//

package importer

import (
	"encoding/json"
	"errors"
	"io"
	"math"
	"net/url"
	"sync/atomic"
	"time"
)

const (
	DefaultBacklog = 100

	SourceSchemeAttachment = "attachment"
)

//******* Errors

var (
	ErrNotImplemented        = errors.New("not implemented")
	ErrNotFound              = errors.New("not found")
	ErrSourceNotSupported    = errors.New("source uri format is not supported")
	ErrImportNotRunning      = errors.New("import not running")
	ErrSourceAlreadyAttached = errors.New("source is already attached")
	ErrFileNotNew            = errors.New("importing already running or done")
	ErrTooManyJobs           = errors.New("too many pending jobs")
	ErrAlreadyCanceled       = errors.New("job is already canceled")
)

//******* Source URL

type SourceURL url.URL

func (s *SourceURL) String() string {
	return (*url.URL)(s).String()
}

func (s *SourceURL) MarshalText() (data []byte, err error) {
	data = []byte(s.String())
	return
}

func (s *SourceURL) UnmarshalText(data []byte) (err error) {
	_, err = (*url.URL)(s).Parse(string(data))
	return
}

//******* Source

type JobSource struct {
	len uint64
	r   io.Reader
}

//******* State

type JobState uint32

const (
	// it is important that JobCanceled is smaller than any other state of the job
	// see job.Cancel() for why this is!
	JobCanceled JobState = iota
	JobNew
	JobInitializing
	JobPending
	JobRunning
	JobDestroying
)

func (s *JobState) String() string {
	switch JobState(atomic.LoadUint32((*uint32)(s))) {
	case JobNew:
		return "new"
	case JobInitializing:
		return "initializing"
	case JobPending:
		return "pending"
	case JobRunning:
		return "running"
	case JobDestroying:
		return "destroying"
	}
	return "unknown"
}

func (s *JobState) fromString(str string) error {
	switch str {
	case "new":
		atomic.StoreUint32((*uint32)(s), uint32(JobNew))
	case "initializing":
		atomic.StoreUint32((*uint32)(s), uint32(JobInitializing))
	case "pending":
		atomic.StoreUint32((*uint32)(s), uint32(JobPending))
	case "running":
		atomic.StoreUint32((*uint32)(s), uint32(JobRunning))
	case "destroying":
		atomic.StoreUint32((*uint32)(s), uint32(JobDestroying))
	default:
		return errors.New("invalid job state: '" + str + "'")
	}
	return nil
}

func (s *JobState) MarshalText() (data []byte, err error) {
	data = []byte(s.String())
	return
}

func (s *JobState) UnmarshalText(data []byte) (err error) {
	return s.fromString(string(data))
}

//******* Progress

type JobProgressStep uint32

const (
	StepFetching JobProgressStep = iota
	StepNormalizing
)

func (s JobProgressStep) String() string {
	switch s {
	case StepFetching:
		return "fetching"
	case StepNormalizing:
		return "normalizing"
	}
	return "unknown"
}

func (s JobProgressStep) MarshalText() (data []byte, err error) {
	data = []byte(s.String())
	return
}

type JobProgress uint64

func (p *JobProgress) set(step JobProgressStep, progress float32) {
	tmp := uint64(step) << 32
	tmp |= uint64(math.Float32bits(progress))
	atomic.StoreUint64((*uint64)(p), tmp)
}

func (p *JobProgress) get() (step JobProgressStep, progress float32) {
	tmp := atomic.LoadUint64((*uint64)(p))
	step = JobProgressStep(tmp >> 32)
	progress = math.Float32frombits(uint32(tmp))
	return
}

func (p *JobProgress) MarshalJSON() ([]byte, error) {
	step, progress := p.get()
	return json.Marshal(struct {
		Step     JobProgressStep `json:"step"`
		Progress float32         `json:"progress"`
	}{step, progress})
}

//******* Timestamp

type Timestamp int64

func (t *Timestamp) set(ts time.Time) {
	atomic.StoreInt64((*int64)(t), ts.Unix())
}

func (t *Timestamp) get() (ts time.Time) {
	return time.Unix(atomic.LoadInt64((*int64)(t)), 0)
}

func (t *Timestamp) MarshalJSON() (data []byte, err error) {
	return t.get().MarshalJSON()
}