#ifndef MAXDIGITS
    #define MAXDIGITS 8
#endif

#ifndef MARET_SevenSegment_h
#define MARET_SevenSegment_h

#if defined(ARDUINO) && ARDUINO >= 100
    #include "Arduino.h"
#else
    #include "WProgram.h"
#endif

#define CC      0 // Common Cathode
#define CA      1 // Common Anode
#define NPN     2
#define PNP     3
#define NP_CC   1
#define NP_CA   00

class MARET_SevenSegment {
    public:
        MARET_SevenSegment(uint8_t hardwareConfig, uint8_t numDigits, const uint8_t digitPins[], const uint8_t segmentPins[], bool resOn = false, bool updateWDelay = false, bool leadZero = false, bool disDecPoint = false);

        void refreshDisplay();
        void begin();

        void setBrightness(int16_t brightness);
        
        void setNumber(int32_t num, int8_t decPlace = -1, bool hex = false);
        void setNumberFloat(float num, int8_t decPlace = -1, bool hex = false);

        void setSegments(const uint8_t segs[]);
        void getSegments(uint8_t segs[]);
        void setSegmentsDigit(const uint8_t digitNum, const uint8_t segs);
        void setChars(const char str[]);
        void blank(void);

    private:
        void setNewNum(int32_t num, int8_t decPlace, bool hex = false);
        void findDigits(int32_t num, int8_t decPlace, bool hex, uint8_t digits[]);
        void setDigitCodes(const uint8_t nums[], int8_t decPlace);
        void segmentOn(uint8_t segmentNum);
        void segmentOff(uint8_t segmentNum);
        void digitOn(uint8_t digitNum);
        void digitOff(uint8_t digitNum);

        uint8_t _hardwareConfig;
        uint8_t digitOnVal, digitOffVal, segmentOnVal, segmentOffVal;
        bool _resOn, _updateWDelay, _leadZero;
        uint8_t _digitPins[MAXDIGITS];
        uint8_t _segmentPins[8];
        uint8_t _numDigits;
        uint8_t _numSegments;
        uint8_t prevUpdateIdx;
        uint8_t digitCodes[MAXDIGITS];
        uint32_t prevUpdateTime;
        uint16_t ledOnTime;
        uint16_t waitOffTime;
        bool waitOffActive;
};

#endif

#define BLANK_IDX       36
#define DASH_IDX        37
#define PERIOD_IDX      38
#define ASTERISK_IDX    39
#define UNDERSCORE_IDX  40

static const int32_t powersOf10[] = {
    1,
    10,
    100,
    1000,
    10000,
    100000,
    1000000,
    10000000,
    100000000,
    1000000000
};

static const int32_t powersOf16[] = {
    0x1,
    0x10,
    0x100,
    0x1000,
    0x10000,
    0x100000,
    0x1000000,
    0x10000000
};

static const uint8_t digitCodeMap[] = {
    // GFEDCBA  Segments      7-segment map:
    0b00111111, // 0   "0"          AAA
    0b00000110, // 1   "1"         F   B
    0b01011011, // 2   "2"         F   B
    0b01001111, // 3   "3"          GGG
    0b01100110, // 4   "4"         E   C
    0b01101101, // 5   "5"         E   C
    0b01111101, // 6   "6"          DDD
    0b00000111, // 7   "7"
    0b01111111, // 8   "8"
    0b01101111, // 9   "9"
    0b01110111, // 65  'A'
    0b01111100, // 66  'b'
    0b00111001, // 67  'C'
    0b01011110, // 68  'd'
    0b01111001, // 69  'E'
    0b01110001, // 70  'F'
    0b00111101, // 71  'G'
    0b01110110, // 72  'H'
    0b00110000, // 73  'I'
    0b00001110, // 74  'J'
    0b01110110, // 75  'K'  Same as 'H'
    0b00111000, // 76  'L'
    0b00000000, // 77  'M'  NO DISPLAY
    0b01010100, // 78  'n'
    0b00111111, // 79  'O'
    0b01110011, // 80  'P'
    0b01100111, // 81  'q'
    0b01010000, // 82  'r'
    0b01101101, // 83  'S'
    0b01111000, // 84  't'
    0b00111110, // 85  'U'
    0b00111110, // 86  'V'  Same as 'U'
    0b00000000, // 87  'W'  NO DISPLAY
    0b01110110, // 88  'X'  Same as 'H'
    0b01101110, // 89  'y'
    0b01011011, // 90  'Z'  Same as '2'
    0b00000000, // 32  ' '  BLANK
    0b01000000, // 45  '-'  DASH
    0b10000000, // 46  '.'  PERIOD
    0b01100011, // 42  '*'  DEGREE ..
    0b00001000, // 95  '_'  UNDERSCORE
};

const uint8_t * const numeralCodes  = digitCodeMap;
const uint8_t * const alphaCodes    = digitCodeMap + 10;

MARET_SevenSegment::MARET_SevenSegment(uint8_t hardwareConfig, uint8_t numDigits, const uint8_t digitPins[], const uint8_t segmentPins[], bool resOn, bool updateWDelay, bool leadZero, bool disDecPoint) {
    _hardwareConfig = hardwareConfig;
    _numDigits      = numDigits;
    _numSegments    = disDecPoint ? 7 : 8;
    _resOn          = resOn;
    _updateWDelay   = updateWDelay;
    _leadZero       = leadZero;
    ledOnTime       = 2000;
    waitOffTime     = 0;
    waitOffActive   = false;
    prevUpdateIdx   = 0;
    prevUpdateTime  = 0;

    for (uint8_t segmentNum = 0; segmentNum < _numSegments; segmentNum++) _segmentPins[segmentNum] = segmentPins[segmentNum];

    for (uint8_t digitNum = 0; digitNum < _numDigits; digitNum++) _digitPins[digitNum] = digitPins[digitNum];
}

void MARET_SevenSegment::begin() {
    if (_numDigits > MAXDIGITS) _numDigits = MAXDIGITS;

    switch (_hardwareConfig) {
        case 0: // Common Cathode
            digitOnVal      = LOW;
            segmentOnVal    = HIGH;
            break;

        case 1: // Common Anode
            digitOnVal      = HIGH;
            segmentOnVal    = LOW;
            break;

        case 2: // With active-high, low-side switches (most commonly N-type FETs)
            digitOnVal      = HIGH;
            segmentOnVal    = HIGH;
            break;

        case 3: // With active low, high side switches (most commonly P-type FETs)
            digitOnVal      = LOW;
            segmentOnVal    = LOW;
            break;
    }

    if (digitOnVal == HIGH) digitOffVal = LOW;
    else digitOffVal = HIGH;

    if (segmentOnVal == HIGH) segmentOffVal = LOW;
    else segmentOffVal = HIGH;

    for (uint8_t digit = 0; digit < _numDigits; digit++) {
        pinMode(_digitPins[digit], OUTPUT);
        digitalWrite(_digitPins[digit], digitOffVal);
    }

    for (uint8_t segmentNum = 0; segmentNum < _numSegments; segmentNum++) {
        pinMode(_segmentPins[segmentNum], OUTPUT);
        digitalWrite(_segmentPins, segmentOffVal);
    }

    blank();
}

void MARET_SevenSegment::refreshDisplay() {
    if (!_updateWDelay) {
        uint32_t us = micros();

        if (waitOffActive) {
            if ((us - prevUpdateTime) < waitOffTime) return;
        } else {
            if ((us - prevUpdateTime) < ledOnTime) return;
        }
        prevUpdateTime = us;

        if (!_resOn) {
            if (waitOffActive) waitOffActive = false;
            else {
                segmentOff(prevUpdateIdx);

                if(waitOffTime) {
                    waitOffActive = true;
                    return;
                }
            }

            prevUpdateIdx++;
            if (prevUpdateIdx >= _numSegments) prevUpdateIdx = 0;

            segmentOn(prevUpdateIdx);
        } else {
            if (waitOffActive) waitOffActive = false;
            else {
                digitOff(prevUpdateIdx);

                if (waitOffTime) {
                    waitOffActive = true;
                    return;
                }
            }

            prevUpdateIdx++;
            if (prevUpdateIdx >= _numDigits) prevUpdateIdx = 0;

            digitOn(prevUpdateIdx);
        }
    } else {
        if (!_resOn) {
            for (uint8_t segmentNum = 0; segmentNum < _numSegments; segmentNum++) {
                segmentOn(segmentNum);
                delayMicroseconds(ledOnTime);
                segmentOff(segmentNum);

                if (waitOffTime) delayMicroseconds(waitOffTime);
            }
        } else {
            for (uint8_t digitNum = 0; digitNum < _numDigits; digitNum++) {
                digitOn(digitNum);
                delayMicroseconds(ledOnTime);
                digitOff(digitNum);

                if (waitOffTime) delayMicroseconds(waitOffTime);
            }
        }
    }
}

void MARET_SevenSegment::segmentOn(uint8_t segmentNum) {
    digitalWrite(_segmentPins[segmentNum], segmentOnVal);
    for (uint8_t digitNum = 0; digitNum < _numDigits; digitNum++) {
        if (digitCodes[digitNum] & (1 << segmentNum)) digitalWrite(_digitPins[digitNum], digitOnVal);
    }
}

void MARET_SevenSegment::segmentOff(uint8_t segmentNum) {
    for (uint8_t digitNum = 0; digitNum < _numDigits; digitNum++) digitalWrite(_digitPins[digitNum], digitOffVal);
    digitalWrite(_segmentPins[segmentNum], segmentOffVal);
}

void MARET_SevenSegment::digitOn(uint8_t digitNum) {
    digitalWrite(_digitPins[digitNum], digitOnVal);
    for (uint8_t segmentNum = 0; segmentNum < _numSegments; segmentNum++) {
        if (digitCodes[digitNum] & (1 << segmentNum)) digitalWrite(_segmentPins[segmentNum], segmentOnVal);
    }
}

void MARET_SevenSegment::digitOff(uint8_t digitNum) {
    for (uint8_t segmentNum = 0; segmentNum < _numSegments; segmentNum++) digitalWrite(_segmentPins[segmentNum], segmentOffVal);
    digitalWrite(_digitPins[digitNum], digitOffVal);
}

void MARET_SevenSegment::setBrightness(int16_t brightness) {
    brightness = constrain(brightness, -200, 200);
    if (brightness > 0) {
        ledOnTime       = map(brightness, 0, 100, 1, 2000);
        waitOffTime     = 0;
        waitOffActive   = false;
    } else {
        ledOnTime       = 0;
        waitOffTime     = map(brightness, 0, -100, 1, 2000);
    }
}

void MARET_SevenSegment::setNumber(int32_t num, int8_t decPlace, bool hex) { setNewNum(num, decPlace, hex); }

void MARET_SevenSegment::setNumberFloat(float num, int8_t decPlace, bool hex) {
    int8_t decPlacePos = constrain(decPlace, 0, MAXDIGITS);
    if (hex) num = num * powersOf16[decPlacePos];
    else num = num * powersOf10[decPlacePos];

    num += (num >= 0.f) ? 0.5f : -0.5f;
    setNewNum((int32_t)num, (int8_t)decPlace, hex);
}

void MARET_SevenSegment::setNewNum(int32_t num, int8_t decPlace, bool hex) {
    uint8_t digits[MAXDIGITS];
    findDigits(num, decPlace, hex, digits);
    setDigitCodes(digits, decPlace);
}

// Bit-segment mapping:  0bHGFEDCBA
//      Visual mapping:
//                        AAAA          0000
//                       F    B        5    1
//                       F    B        5    1
//                        GGGG          6666
//                       E    C        4    2
//                       E    C        4    2        (Segment H is often called
//                        DDDD  H       3333  7      DP, for Decimal Point)

void MARET_SevenSegment::setSegments(const uint8_t segs[]) { for (uint8_t digit = 0; digit < _numDigits; digit++) digitCodes[digit] = segs[digit]; }

void MARET_SevenSegment::setSegmentsDigit(const uint8_t digitNum, const uint8_t segs) { if (digitNum < _numDigits) digitCodes[digitNum] = segs; }

void MARET_SevenSegment::getSegments(uint8_t segs[]) { for (uint8_t digit = 0; digit < _numDigits; digit++) segs[digit] = digitCodes[digit]; }

void MARET_SevenSegment::setChars(const char str[]) {
    for (uint8_t digit = 0; digit < _numDigits; digit++) digitCodes[digit] = 0;

    uint8_t strIdx = 0;
    for (uint8_t digitNum = 0; digitNum < _numDigits; digitNum++) {
        char ch = str[strIdx];
        if (ch == '\0') break;
        if (ch >= '0' && ch <= '9') digitCodes[digitNum] = numeralCodes[ch - '0'];
        else if (ch >= 'A' && ch <= 'Z') digitCodes[digitNum] = alphaCodes[ch - 'A'];
        else if (ch >= 'a' && ch <= 'z') digitCodes[digitNum] = alphaCodes[ch - 'a'];
        else if (ch == ' ') digitCodes[digitNum] = digitCodeMap[BLANK_IDX];
        else if (ch == '.') digitCodes[digitNum] = digitCodeMap[PERIOD_IDX];
        else if (ch == '*') digitCodes[digitNum] = digitCodeMap[ASTERISK_IDX];
        else if (ch == '_') digitCodes[digitNum] = digitCodeMap[UNDERSCORE_IDX];
        else digitCodes[digitNum] = digitCodeMap[DASH_IDX];

        strIdx++;
        if (str[strIdx] == '.') {
            digitCodes[digitNum] |= digitCodeMap[PERIOD_IDX];
            strIdx++;
        }
    }
}

void MARET_SevenSegment::blank(void) {
    for (uint8_t digitNum = 0; digitNum < _numDigits; digitNum++) digitCodes[digitNum] = digitCodeMap[BLANK_IDX];
    segmentOff(0);
    digitOff(0);
}

void MARET_SevenSegment::findDigits(int32_t num, int8_t decPlace, bool hex, uint8_t digits[]) {
    const int32_t * powersOfBase    = hex ? powersOf16 : powersOf10;
    const int32_t maxNum            = powersOfBase[_numDigits] - 1;
    const int32_t minNum            = -(powersOfBase[_numDigits - 1] - 1);

    if (num > maxNum || num < minNum) for (uint8_t digitNum = 0; digitNum < _numDigits; digitNum++) digits[digitNum] = DASH_IDX;
    else {
        uint8_t digitNum = 0;

        if (num < 0) {
            digits[0]   = DASH_IDX;
            digitNum    = 1;
            num         = -num;
        }

        for (; digitNum < _numDigits; digitNum++) {
            int32_t factor      = powersOfBase[_numDigits - 1 - digitNum];
            digits[digitNum]    = num / factor;
            num                -= digits[digitNum] * factor;
        }

        if (decPlace < 0) decPlace = 0;
        if (!_leadZero) {
            for (digitNum = 0; digitNum < (_numDigits - 1 - decPlace); digitNum++) {
                if (digits[digitNum] == 0) digits[digitNum] = BLANK_IDX;
                else if (digits[digitNum] <= 9) {
                    break;
                }
            }
        }
    }
}

void MARET_SevenSegment::setDigitCodes(const uint8_t digits[], int8_t decPlace) {
    for (uint8_t digitNum = 0; digitNum < _numDigits; digitNum++) {
        digitCodes[digitNum] = digitCodeMap[digits[digitNum]];
        if (decPlace >= 0) if (digitNum == _numDigits - 1 - decPlace) digitCodes[digitNum] |= digitCodeMap[PERIOD_IDX];
    }
}

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:

}