import QtQuick 2.3
import QtMultimedia 5.0
import "binarydict.js" as Wordlist
import QtQuick.Particles 2.0
import QtQuick.LocalStorage 2.0
import Ubuntu.Components 1.1
import Ubuntu.Components.Popups 1.0

MainView {
    id: mainView
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"
    applicationName: "com.ubuntu.dropping-letters"
    width: units.gu(45) // 48 * 7 + 2 * 7 + 2
    height: units.gu(65) // 48 * 10 + 2 * 10 + 50


    function resetGame () {
        lm.clear();
        for (var i=0; i<7; i++) {
            lm.append({'letters': [] });
        }
        accum.text = "";
        droptimer.tickCount = 0;
        flipable.flipped = true;
        bestscore.updateScore(main.score);
        main.score = 0;
        droptimer.start();
        toolbar.opened = false;
        game.paused = false;
        game.ended = false;
    }

    Page {
        title: i18n.tr("Dropping Letters")
        tools: ToolbarItems {
            id: toolbar
            ToolbarButton {
                text: i18n.tr("Sound")
                iconName: volume.audible ? "speaker" : "speaker-mute"
                visible: volume.supportsMedia
                onTriggered: {
                    volume.audible = !volume.audible;
                }
            }
            ToolbarButton {
                objectName: "new_game"
                text: i18n.tr("New game")
                iconName: "add"
                onTriggered: {
                    resetGame();
                }
            }
            ToolbarButton {
                objectName: "pause_game"
                text: game.paused ? i18n.tr("Resume") : i18n.tr("Pause")
                iconName: game.paused ? "media-playback-start": "media-playback-pause"
                enabled: flipable.flipped && !game.ended
                onTriggered: {
                    if (game.paused) {
                        droptimer.start();
                    } else {
                        droptimer.stop();
                    }

                    game.paused = !game.paused;
                }
            }
        }

        Item {
            id: volume
            property int frame: 0
            property bool reverse: false
            property bool audible: true
            // we are using this to disable audio playback, it is breaking
            // on devices.  Set this to true when playback is fixed or
            // find a way to fail gracefully.  When this can be enabled again,
            // be sure to uncomment all the Audio elements
            property bool supportsMedia: true
        }

        Flipable {
            id: flipable
            objectName: "flipable"
            property bool flipped: false
            property variant db: null
            anchors.fill: parent

            transform: Rotation {
                id: rotation
                origin.x: flipable.width/2
                origin.y: flipable.height/2
                axis.x: 0; axis.y: 1; axis.z: 0     // set axis.y to 1 to rotate around y-axis
                angle: 0    // the default angle
            }
            property int minChromeHeight: 50

            Component.onCompleted: {
                header.visible = false
                var db = LocalStorage.openDatabaseSync("dropping-letters", "1.0", "Dropping Letters", 1000);
                db.transaction(function(tx) {
                    // Create the database if it doesn't already exist
                    tx.executeSql('CREATE TABLE IF NOT EXISTS Scores(score INT, time TEXT)');
                });
                bestscore.updateScore(0);
            }

            states: [
                State {
                    name: "back"
                    PropertyChanges { target: rotation; angle: 180 }
                    when: flipable.flipped
                },
                State {
                    name: "front"
                    when: !flipable.flipped
                }
            ]

            transitions: [
                Transition {
                    NumberAnimation { target: rotation; property: "angle"; duration: 200 }
                }
            ]

            front: Rectangle {
                id: entry
                color: "#efefea"
                width: flipable.width // 48 * 7 + 2 * 7 + 2
                height: flipable.height // 48 * 10 + 2 * 10 + 50

                Column {
                    id: logo
                    anchors.centerIn: parent
                    //spacing: units.dp(2) // 2
                    Repeater {
                        id: titleRowRepeater
                        model: ["DROP", "PING", "LETT", "ERS "]
                        Row {
                            id: titleRow
                            //spacing: units.dp(2) // 2
                            property int lineIndex: index
                            property string modelString: modelData
                            Repeater {
                                id: titleColRepeater
                                model: modelData.split("")
                                Rectangle {
                                    id: titleletter
                                    width: Math.max(flipable.width * 0.4, flipable.height * 0.4) / titleRowRepeater.count - titleRowRepeater.count * titleRow.spacing
                                    height: Math.max(flipable.width * 0.4, flipable.height * 0.4) / titleRowRepeater.count - titleRowRepeater.count * titleRow.spacing
                                    color: Qt.hsla(210/360, 0.84, 0.25 + ((4-parent.lineIndex) * 0.05) + ((4-index) * 0.05));
                                    //radius: "medium"
                                    Label {
                                        objectName: "lettertile"
                                        text: modelData
                                        anchors.centerIn: parent
                                        fontSize: "x-large"
                                        font.bold: true
                                        color: "#ffffff"
                                    }
                                    states: [
                                        State { name: "showing"; when: flipable.state == "front"
                                            PropertyChanges { target: titleletter; y:  0 }
                                        },
                                        State { name: "notshowing"; when: flipable.state != "front"
                                            PropertyChanges { target: titleletter; y: -units.gu(300) }
                                        }
                                    ]
                                    Behavior on y {
                                        SequentialAnimation {
                                            PauseAnimation {
                                                duration: (35 * parent.modelString.length * (4-parent.lineIndex)) + (
                                                    35 * index) }
                                            NumberAnimation { easing.type: Easing.OutQuart }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                Label {
                    id: playbutton
                    objectName: "playButton"
                    text: i18n.tr("Play")
                    anchors.top: logo.bottom
                    anchors.topMargin: units.gu(2)
                    anchors.horizontalCenter: logo.horizontalCenter
                    fontSize: "x-large"
                    color: "#222222"
                    MouseArea {
                        anchors.fill: parent
                        enabled: !flipable.flipped
                        onClicked: {
                            resetGame();
                        }
                    }
                }
                Label {
                    id: bestscore
                    objectName: "bestscorelabel"
                    property int bestsofar: 0
                    text: "..."
                    anchors.top: playbutton.bottom
                    anchors.topMargin: units.gu(2)
                    anchors.horizontalCenter: logo.horizontalCenter
                    fontSize: "large"
                    color: "#222222"
                    function updateScore(score) {
                        var db = LocalStorage.openDatabaseSync("dropping-letters", "1.0", "Dropping Letters", 1000);
                        if (score > bestscore.bestsofar) {
                            db.transaction(function(tx) {
                                tx.executeSql("insert into Scores values (?,?)", [main.score, 0]);
                            });
                            bestscore.bestsofar = score;
                        } else if (score === 0) {
                            db.transaction(function(tx) {
                                var res = tx.executeSql('SELECT score from Scores order by score DESC LIMIT 1');
                                if (res.rows.length > 0) {
                                    bestscore.bestsofar = res.rows.item(0).score;
                                }
                            });
                        }
                        bestscore.text = "Best score: " + bestscore.bestsofar
                    }
                }

                Rectangle {
                    id: helpbutton
                    objectName: "introhelpbutton"
                    width: units.gu(5)
                    height: units.gu(5)
                    radius: units.gu(1)
                    Label {
                        objectName: "introhelplabel"
                        text: "?"
                        anchors.centerIn: parent
                    }
                    anchors.top: parent.top
                    anchors.right: parent.right
                    anchors.margins: units.gu(2)
                    MouseArea {
                        anchors.fill: parent
                        enabled: !flipable.flipped
                        onClicked: {
                            PopupUtils.open(helppop, helpbutton)
                        }
                    }
                }
                Component {
                    id: helppop
                    Popover {
                        id: helpsheet
                        objectName: "helpsheetpopover"
                        Rectangle {
                            objectName: "helpsheetpopoverbox"
                            anchors.top: parent.top
                            anchors.left: parent.left
                            anchors.right: parent.right
                            height: units.gu(15)
                            border.color: "#ddd"
                            radius: 2
                            Label {
                                objectName: "helpsheetpopoverlabel"
                                anchors.fill: parent
                                anchors.margins: units.gu(1)
                                text: "game by <a href='http://twitter.com/sil'>@sil</a><br>music: <a href='http://incompetech.com/music/royalty-free/index.html?isrc=USUAN1200076'>Easy Lemon</a> from Kevin McCloud (incompetech.com)<br>sounds: freesound from <a href='http://www.freesound.org/people/kantouth/sounds/106727/'>kantouth</a> / <a href='http://www.freesound.org/people/tictacshutup/sounds/407/'>tictacshutup</a> / <a href='http://www.freesound.org/people/dj-chronos/sounds/45137/'>dj-chronos</a> / <a href='http://www.freesound.org/people/justinbw/sounds/80921/'>justinbw</a>"
                                wrapMode: Text.WordWrap
                                textFormat: Text.StyledText
                                onLinkActivated: Qt.openUrlExternally(link)
                            }
                        }
                    }
                }

            }

            back: Rectangle {
                id: main
                objectName: "lettersRectangles"
                color: "#58585A"
                width: flipable.width // 48 * 7 + 2 * 7 + 2
                height: flipable.height // 48 * 10 + 2 * 10 + 50
                property var selectedItems: []
                /*
                obtained with:
                import re; fp=open('/usr/share/dict/words');
                shorts=[re.sub('[^a-zA-Z]','',x.lower().strip()) for x in fp if len(x)<7];
                from collections import Counter; c=Counter(''.join(shorts)); least=c.most_common()[-1][1];
                print dict([(x.upper(),c[x]/least) for x in c])
                */
                property variant letterFreqs: {
                    'A': 66, 'C': 22, 'B': 19, 'E': 72, 'D': 28, 'G': 18, 'F': 12, 'I': 41, 'H': 20,
                    'K': 14, 'J': 4, 'M': 22, 'L': 40, 'O': 48, 'N': 35, 'Q': 1, 'P': 22, 'S': 75,
                    'R': 43, 'U': 26, 'T': 37, 'W': 13, 'V': 7, 'Y': 19, 'X': 3, 'Z': 4
                }
                property variant letterScores: {
                    "A":1,"B":3,"C":3,"D":2,"E":1,"F":4,"G":2,"H":4,"I":1,"J":8,"K":5,"L":1,
                    "M":3,"N":1,"O":1,"P":3,"Q":10,"R":1,"S":1,"T":1,"U":1,"V":4,"W":4,"X":8,
                    "Y":4,"Z":10
                }
                property int score: 0

                function getRandomWeightedLetter() {
                    // could work out sumOfWeights once, but this is easier to understand
                    var sumOfWeights = 0;
                    for (var k in main.letterFreqs) { sumOfWeights += main.letterFreqs[k]; }
                    var selection = Math.floor(Math.random() * sumOfWeights);
                    for (var k in main.letterFreqs) {
                        if (selection < main.letterFreqs[k]) return k;
                        selection -= main.letterFreqs[k];
                    }
                }

                /* Allow sounds but not music for now as music plays on, allow this once
                  bug 1315454 is fixed
                Audio {
                    id: music
                    source: "Easy_Lemon_60_second.ogg"
                    autoPlay: volume.supportsMedia
                    onStopped: {
                        if (volume.supportsMedia && volume.audible) {
                            music.play();
                        }
                    }
                }
                */

                Audio {
                    id: click
                    source: "407__tictacshutup__click-1-off-click.ogg"
                    autoLoad: volume.supportsMedia
                }

                Audio {
                    id: success
                    source: "80921__justinbw__buttonchime02up.ogg"
                    autoLoad: volume.supportsMedia
                }

                Audio {
                    id: failure
                    source: "106727__kantouth__cartoon-bing-low.ogg"
                    autoLoad: volume.supportsMedia
                }

                Audio {
                    id: gameover
                    source: "45137__dj-chronos__dark-church-bell.ogg"
                    autoLoad: volume.supportsMedia
                    onStopped: {
                        flipable.flipped = false
                    }
                }

                Rectangle {
                    id: bottombar
                    anchors {
                        bottom: main.bottom
                        left: main.left
                        right: main.right
                    }
                    height: units.gu(5)
                    color: Qt.hsla(210/360, 0.84, 0.2)

                    Item {
                        objectName: "mainscore"
                        id: mainscore
                        anchors {
                            centerIn: bottombar
                            bottom: bottombar.bottom
                        }
                        Label {
                            anchors.centerIn: parent
                            fontSize: "x-large"
                            text: main.score
                            color: "white"
                        }
                    }
                }

                Rectangle {
                    id: topbar
                    color: "blue"
                    anchors.top: main.top
                    anchors.left: main.left
                    anchors.right: main.right
                    height: (main.height - (game.squaresize * 10 + 11)) / 2
                    z: 2

                    Rectangle {
                        anchors.fill: topbar
                        z: 2
                        color: accum.text == "" ? "#efefea" : (accum.isValid ? Qt.hsla(124/360, 0.83, 0.45) : Qt.hsla(0, 0.83, 0.65))
                        id: suggestedWordContainer
                        Label {
                            id: accum
                            objectName: "accumulate"
                            anchors.centerIn: parent
                            text: ""
                            color: "white"
                            fontSize: "x-large"
                            font.bold: true
                            property bool isValid: false
                            function findBinaryWord( word ) {
                                var bd = Wordlist.wordlist;
                                var l = word.length;
                                // Don't search if there's nothing to look through
                                if ( !bd[l] ) {
                                    return false;
                                }
                                // Get the number of words in the dictionary bin
                                var words = bd[l].length / l,
                                // The low point from where we're starting the binary search
                                low = 0,
                                // The max high point
                                high = words - 1,
                                // And the precise middle of the search
                                mid = Math.floor( words / 2 );
                                // We continue to look until we reach a final word
                                while ( high >= low ) {
                                    // Grab the word at our current position
                                    var found = bd[l].substr( l * mid, l );
                                    // If we've found the word, stop now
                                    if ( word === found ) {
                                        return true;
                                    }
                                    // Otherwise, compare
                                    // If we're too high, move lower
                                    if ( word < found ) {
                                        high = mid - 1;
                                    // If we're too low, go higher
                                    } else {
                                        low = mid + 1;
                                    }
                                    // And find the new search point
                                    mid = Math.floor( (low + high) / 2 );
                                }
                                // Nothing was found
                                return false;
                            }
                            onTextChanged: {
                                accum.isValid = findBinaryWord(accum.text);
                            }
                        }
                        function processSuggestedWord() {
                            if (accum.isValid) {
                                if (volume.supportsMedia && volume.audible) {
                                    success.play();
                                }
                                var thisscore = 0, wordlength = accum.text.length;
                                accum.text = "";

                                // tell the boxes to destroy themselves
                                main.selectedItems.forEach(function(b) {
                                    thisscore += main.letterScores[b.containedLetter];
                                    b.state = "dead";
                                })
                                main.selectedItems = [];
                                main.score += thisscore * wordlength;
                                scoredisplay.text = "" + (thisscore * wordlength);
                                showscoredisplay.start();
                            } else {
                                if (volume.supportsMedia && volume.audible) {
                                    failure.play();
                                }
                                accum.text = "";
                                main.selectedItems.forEach(function(b) { b.selected = false; })
                                main.selectedItems = [];
                            }
                        }
                        focus: true
                        Keys.onReturnPressed: {
                            suggestedWordContainer.processSuggestedWord();
                        }
                        MouseArea {
                            anchors.fill: parent
                            onClicked: { suggestedWordContainer.processSuggestedWord(); }
                        }
                    }


                }

                Label {
                    id: scoredisplay
                    objectName: "scoredisplaylabel"
                    anchors.centerIn: parent
                    z: 3
                    fontSize: "x-large"
                    text: "200"
                    color: "red"
                    opacity: 0
                }

                ParallelAnimation {
                    id: showscoredisplay
                    NumberAnimation {
                        property: "scale"
                        from: 0.1
                        to: 8.0
                        duration: 400
                        target: scoredisplay
                    }
                    SequentialAnimation {
                        NumberAnimation {
                            property: "opacity"
                            from: 0
                            to: 1.0
                            duration: 20
                            target: scoredisplay
                        }
                        NumberAnimation {
                            property: "opacity"
                            from: 1.0
                            to: 0
                            duration: 380
                            target: scoredisplay
                        }
                    }
                }


                Timer {
                    id: droptimer
                    objectName: "dropTimer"
                    repeat: true
                    running: flipable.flipped
                    interval: 2000
                    property int tickCount: 0
                    triggeredOnStart: true
                    onTriggered: {
                        tickCount += 5
                        droptimer.interval = 2000 - tickCount
                        var idx = Math.round(Math.random() * (lm.count - 1));
                        lm.get(idx).letters.append({ letter: main.getRandomWeightedLetter() });
                        if (lm.get(idx).letters.count >= 10) {
                            droptimer.stop();
                            game.ended = true;
                            if (volume.supportsMedia && volume.audible) {
                                gameover.play();
                            }
                            bestscore.updateScore(main.score);
                        }
                    }
                }

                ListModel {
                    id: lm
                }


                Rectangle {
                    id: game
                    objectName: "gametile"
                    property int squaresize: Math.min((flipable.width) / 7, (flipable.height - (flipable.minChromeHeight * 2)) / 10)
                    property bool ended: false;
                    property bool paused: false;
                    anchors.top: topbar.bottom
                    anchors.bottom: bottombar.top
                    anchors.left: main.left
                    anchors.right: main.right
                    scale: -1
                    color: "#efefef"
                    Row {
                        anchors.horizontalCenter: game.horizontalCenter
                        anchors.top: game.top
                        anchors.topMargin: 1
                        //spacing: 1
                        Repeater {
                            model: lm
                            Column {
                                //spacing: 1
                                property int idx: index
                                width: game.squaresize
                                height: game.height
                                add: Transition {
                                    NumberAnimation { properties: "y"; easing.type: Easing.OutBounce; duration: 1000 }
                                }
                                move: Transition {
                                    NumberAnimation { properties: "y"; easing.type: Easing.OutBounce }
                                }
                                Repeater {
                                    model: letters
                                    Rectangle {
                                        id: box
                                        objectName: "gametilebox"
                                        property bool selected: false
                                        property int idx: index
                                        property string containedLetter: letter
                                        color: {
                                            if (lm.get(parent.idx).letters.count >= 10) {
                                                return "red";
                                            } else if (!selected) {
                                                return Qt.hsla(210/360, 0.84, 0.25 + (idx * 0.05));
                                            } else if (accum.isValid) {
                                                return Qt.hsla(124/360, 0.83, 0.45); // "#93cc98";
                                            } else {
                                                return Qt.hsla(0, 0.83, 0.65); // "#cc9598"
                                            }
                                        }
                                        //radius: "medium"
                                        scale: -1
                                        width: game.squaresize
                                        height: game.squaresize
                                        visible: !game.paused
                                        y: game.height + game.squaresize
                                        z: 5
                                        Label {
                                            objectName: "gametilelabel"
                                            anchors.centerIn: parent
                                            text: letter
                                            fontSize: "large"
                                            font.bold: true
                                            color: "#ffffff"
                                        }
                                        MouseArea {
                                            enabled: !game.ended
                                            anchors.fill: parent
                                            onClicked: {
                                                if (!box.selected) {
                                                    box.selected = true;
                                                    accum.text += letter;
                                                    if (volume.supportsMedia && volume.audible) {
                                                        click.play();
                                                    }
                                                    main.selectedItems[main.selectedItems.length] = box;
                                                } else {
                                                    if (box === main.selectedItems[main.selectedItems.length - 1]) {
                                                        main.selectedItems.pop(main.selectedItems.length - 1);
                                                        box.selected = false;
                                                        accum.text = accum.text.substr(0, accum.text.length - 1);
                                                    }
                                                }
                                            }
                                        }
                                        Behavior on opacity {
                                            SequentialAnimation {
                                                ScriptAction { script: pulseEmitter.burst(1000); }
                                                NumberAnimation { properties:"opacity"; duration: 500 }
                                                ScriptAction { script: lm.get(box.parent.idx).letters.remove(box.idx); }
                                            }
                                        }
                                        states: [
                                            State { name: "alive" },
                                            State {
                                                name: "dead"
                                                PropertyChanges { target: box; opacity: 0 }
                                            }
                                        ]
                                        ParticleSystem {
                                            id: particles
                                            width: flipable.width / 2
                                            height: flipable.width / 2
                                            anchors.centerIn: parent
                                            clip: false
                                            ImageParticle {
                                                source: "redStar.png"
                                                alpha: 0
                                                colorVariation: 0.6
                                            }
                                            Emitter {
                                                id: pulseEmitter
                                                x: parent.width/2
                                                y: parent.height/2
                                                emitRate: 2000
                                                lifeSpan: 500
                                                enabled: false
                                                velocity: AngleDirection { magnitude: 256; angleVariation: 360; magnitudeVariation: 200; }
                                                size: parent.width / 8
                                                sizeVariation: 8
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
