package ihw1

import (
	"fmt"
	"io/ioutil"
	embd "netease_control_center/nembd"
	"os"
	"regexp"
	"strconv"
	"strings"
	"sync"
)

type led struct {
	id string

	rbrightness *os.File
	gbrightness *os.File
	bbrightness *os.File

	initialized bool

	r int
	g int
	b int

	lock sync.Mutex
}

type rgb uint8

const (
	red rgb = iota
	green
	blue
)

func NewLED(id string) embd.LED {
	return &led{id: id}
}

func (l *led) init() error {
	if l.initialized {
		return nil
	}

	var err error
	if l.isRgb() == false {
		if l.rbrightness, err = l.brightnessFile(); err != nil {
			return err
		}
	} else {
		if l.rbrightness, err = l.rgbbrightnessFile(red); err != nil {
			return err
		}
		if l.gbrightness, err = l.rgbbrightnessFile(green); err != nil {
			return err
		}
		if l.bbrightness, err = l.rgbbrightnessFile(blue); err != nil {
			return err
		}
	}
	l.r, l.g, l.b = -1, -1, -1

	l.initialized = true

	return nil
}

func (l *led) isRgb() bool {
	return strings.HasPrefix(l.id, "md")
}

func (l *led) brightnessFilePath() string {
	return fmt.Sprintf("/sys/class/leds/%v/brightness", l.id)
}

func toRgbStr(color rgb) string {
	switch color {
	case red:
		return "r"
	case green:
		return "g"
	case blue:
		return "b"
	default:
		return ""
	}
}

func (l *led) rgbbrightnessFilePath(color rgb) string {
	switch {
	case strings.HasPrefix(l.id, "md"):
		rg, _ := regexp.Compile(`[\d]+`)
		strs := rg.FindAllString(l.id, 1)
		if strs != nil {
			no, err := strconv.Atoi(strs[0])
			if err != nil {
				return ""
			}
			return fmt.Sprintf("/sys/class/leds/rgb%02d-%s/brightness", no-1, toRgbStr(color))
		}
	default:
		return l.brightnessFilePath()
	}
	return ""
}

func (l *led) rgbbrightnessFile(color rgb) (*os.File, error) {
	return l.openFile(l.rgbbrightnessFilePath(color))
}

func (l *led) openFile(path string) (*os.File, error) {
	return os.OpenFile(path, os.O_RDWR, os.ModeExclusive)
}

func (l *led) brightnessFile() (*os.File, error) {
	return l.openFile(l.brightnessFilePath())
}

func (l *led) On() error {
	if err := l.init(); err != nil {
		return err
	}

	_, err := l.rbrightness.WriteString("1")
	return err
}

func (l *led) Off() error {
	if err := l.init(); err != nil {
		return err
	}

	_, err := l.rbrightness.WriteString("0")
	return err
}

func (l *led) isOn() (bool, error) {
	l.rbrightness.Seek(0, 0)
	bytes, err := ioutil.ReadAll(l.rbrightness)
	if err != nil {
		return false, err
	}
	str := string(bytes)
	str = strings.TrimSpace(str)
	if str == "1" {
		return true, nil
	}
	return false, nil
}

func (l *led) Toggle() error {
	if err := l.init(); err != nil {
		return err
	}

	state, err := l.isOn()
	if err != nil {
		return err
	}

	if state {
		return l.Off()
	}
	return l.On()
}

func (l *led) Rgb(r int, g int, b int) error {
	if err := l.init(); err != nil {
		return err
	}

	var err error

	_, err = l.rbrightness.WriteString(strconv.Itoa(int(r)))
	if err != nil {
		return err
	}

	_, err = l.gbrightness.WriteString(strconv.Itoa(int(g)))
	if err != nil {
		return err
	}

	_, err = l.bbrightness.WriteString(strconv.Itoa(int(b)))
	if err != nil {
		return err
	}

	return err
}

func (l *led) Close() error {
	if !l.initialized {
		return nil
	}
	if l.isRgb() == false {
		if err := l.rbrightness.Close(); err != nil {
			return err
		}
	} else {
		if err := l.rbrightness.Close(); err != nil {
			return err
		}
		if err := l.gbrightness.Close(); err != nil {
			return err
		}
		if err := l.bbrightness.Close(); err != nil {
			return err
		}
	}

	l.initialized = false

	return nil
}
