# Kanbanara Root
# Written by Rebecca Shalfield between 2013 and 2017
# Copyright (c) Rebecca Shalfield 2013-2017
# Released under the GNU AGPL v3

'''Kanbanara's Root'''

import datetime
import os
import random
import re
import socket
import sys
import time
import zipfile

import cherrypy
from pymongo import MongoClient
import scss


class Root:
    def __init__(self):
        self.current_dir = os.path.dirname(os.path.abspath(__file__))

        self.socket_hostname = socket.gethostname()
        
        self.read_mongodb_ini_file()

        # Connect to MongoDB on given host and port
        connection = MongoClient(self.mongodb_host, self.mongodb_port)

        # Connect to 'kanbanara' database, creating if not already exists
        kanbanara_db = connection['kanbanara']

        # Connect to 'visitors' collection
        self.visitors_collection = kanbanara_db['visitors']

        # Compile website.scss file to create fresh website.css file
        website_scss_path = os.path.join(self.current_dir, 'css', 'website.scss')
        with open(website_scss_path, "r") as input_handle:
            website_scss_code = input_handle.read()
            website_css_code = scss.compiler.compile_string(website_scss_code)
            website_css_file = os.path.join(self.current_dir, 'css', 'website.css')
            with open(website_css_file, 'w') as output_handle:
                output_handle.write(website_css_code)

        # Compile all .scss theme files to create fresh corresponding .css files
        path_to_imports = [self.current_dir+os.sep+'css'+os.sep]
        for filename in os.listdir(os.path.join(self.current_dir, 'css', 'themes')):
            if filename.lower().endswith('.scss'):
                object_scss_path = os.path.join(self.current_dir, 'css', 'themes', filename)
                with open(object_scss_path, "r") as input_handle:
                    object_scss_code = input_handle.read()
                    object_css_code = scss.compiler.compile_string(object_scss_code,
                                                                   search_path=path_to_imports)
                    (leafname, _) = os.path.splitext(filename)
                    object_css_file = os.path.join(self.current_dir, 'css', 'themes', leafname+'.css')
                    with open(object_css_file, 'w') as output_handle:
                        output_handle.write(object_css_code)

        cherrypy.config.update({'error_page.400': self.error_page_400,
                                'error_page.401': self.error_page_401,
                                'error_page.403': self.error_page_403,
                                'error_page.404': self.error_page_404,
                                'error_page.500': self.error_page_500})

        if self.socket_hostname == 'www.kanbanara.com' or os.path.exists(self.current_dir+os.sep+'website'):
            self.create_bleeding_edge_zip_file()

        for folder in ['database']:
            if not os.path.exists(self.current_dir+os.sep+folder):
                os.mkdir(self.current_dir+os.sep+folder)

        for database_folder in ['projects', 'members']:
            if not os.path.exists(self.current_dir+os.sep+'database'+os.sep+database_folder):
                os.mkdir(self.current_dir+os.sep+'database'+os.sep+database_folder)
                
    @cherrypy.expose
    def constitution(self):
        if (self.socket_hostname == 'www.kanbanara.com' or
                os.path.exists(self.current_dir+os.sep+'website')):
            with open(os.path.join(self.current_dir, 'html', 'constitution.html'), 'r') as handle:
                content = handle.read()

            return content
        else:
            raise cherrypy.HTTPRedirect("/kanban/index", 302)

    def create_bleeding_edge_zip_file(self):
        zip_handle = zipfile.ZipFile(os.path.join(self.current_dir, 'downloads',
                                                  'kanbanara_bleedingedge.zip'), mode='w')
        self.write_data_zip_file_contents(self.current_dir, zip_handle)
        zip_handle.close()
        
    def write_data_zip_file_contents(self, current_path, zip_handle):
        for object in os.listdir(current_path):
            if os.path.isdir(current_path+os.sep+object):
                if object not in ['__pycache__', 'attachments', 'database', 'downloads', 'dbdump',
                                  'IPR', 'linted', 'linting', 'logs']:
                    self.write_data_zip_file_contents(current_path+os.sep+object, zip_handle)

            else:
                if object not in ['administrator.ini', 'kanbanara.css', 'kanbanara.ini', 'mongodb.ini', 'website.css', 'AlbumArtSmall.jpg', 'Folder.jpg']:
                    (_, write_path) = os.path.splitdrive(current_path)
                    zip_handle.write(write_path+os.sep+object)

    def read_mongodb_ini_file(self):
        """ Reads the common mongodb.ini file and extracts host and port information """
        self.mongodb_host = 'localhost'
        self.mongodb_port = 27017
        with open(self.current_dir+os.sep+"mongodb.ini", "r") as handle:
            settings = handle.read()

        mongodb_host_pattern = re.compile(r'(?i)host\s?=\s?(\S+)')
        results = mongodb_host_pattern.findall(settings)
        if results != []:
            self.mongodb_host = results[0]

        mongodb_port_pattern = re.compile(r'(?i)port\s?=\s?(\d+)')
        results = mongodb_port_pattern.findall(settings)
        if results != []:
            self.mongodb_port = int(results[0])

    def menubar(self, page=""):
        page_titles = {'contactus':  'Contact Us',
                       'features':   'Features',
                       'forum':      'Forum',
                       'foundation': 'Foundation',
                       'index':      'Home',
                       'slideshow':  'Slideshow',
                       'userguide':  'User Guide',
                       'wiki':       'Wiki'
                      }
        content = []
        entries = []
        content.append(f'<h1 class="menubar">{page_titles[page]} <b class="menubar">')
        if page == 'index':
            entries.append('Home')
        else:
            entries.append('<a href="/">Home</a>')

        if page == 'features':
            entries.append('Features')
        else:
            entries.append('<a href="/features">Features</a>')

        if page == 'slideshow':
            entries.append('Slideshow')
        else:
            entries.append('<a href="/slideshow">Slideshow</a>')

        if page == 'foundation':
            entries.append('Foundation')
        else:
            entries.append('<a href="/foundation">Foundation</a>')

        if page == 'userguide':
            entries.append('User Guide')
        else:
            entries.append('<a href="/userguide">User Guide</a>')

        entries.append('<a href="/authenticate?username=guest@kanbanara.com&password=guest&returnurl=/kanban">Trial Demo</a>')
        entries.append('<a href="/kanban">Personal Session</a>')
        entries.append('<a href="/authenticate?username=support@kanbanara.com&password=support&returnurl=/kanban">Support</a>')

        if page == 'contactus':
            entries.append('Contact Us')
        else:
            entries.append('<a href="/contactus">Contact Us</a>')

        if page == 'forum':
            entries.append('Forum')
        else:
            entries.append('<a href="/forum">Forum</a>')

        if page == 'wiki':
            entries.append('Wiki')
        else:
            entries.append('<a href="/wiki">Wiki</a>')

        if page == 'downloads':
            entries.append('Downloads')
        else:
            entries.append('<a href="/downloads">Downloads</a>')

        content.append(" | ".join(entries))
        content.append('</b></h1>')
        return "".join(content)

    @cherrypy.expose
    def index(self):
        if self.socket_hostname == 'www.kanbanara.com' or os.path.exists(self.current_dir+os.sep+'website'):
            content = '<html><head>'
            content += '<title>Kanbanara</title>'
            content += '<link rel="stylesheet" href="/css/website.css" type="text/css"><script type="text/javascript" src="/scripts/website.js"></script>'
            content += '<script type="text/javascript" src="/scripts/jquery-1.7.2.min.js"></script>'
            content += '</head>'
            content += '<body><div align="center">'

            content += '''<table width="100%" border="0">
                          <tr><td rowspan="2">
                          <table id="kanbanaralogo">
                          <tr><td class="red">K</td><td class="green">A</td><td class="blue">N</td></tr>
                          <tr><td class="blue">B</td><td class="red">A</td><td class="green">N</td></tr>
                          <tr><td class="green">A</td><td class="blue">R</td><td class="red">A</td></tr>
                          </table>
                          </td><td width="100%"><h1 class="header">Kanbanara</h1></td></tr><tr><td><h1 class="catchphrase">A Kanban-Based Project Management System</h1></td></tr>
                          </table>'''

            content += self.menubar('index')
            content += '<h1><table><tr>'
            content += '<td><a href="/images/website/kanbanboard_simple.png"><img class="screenshot" src="/images/website/kanbanboard_simple.png" title="Simple Kanban Board"></a></td>'
            content += '<td><a href="/images/website/backlogsorter.png"><img class="screenshot" src="/images/website/backlogsorter.png" title="Backlog Sorter"></a></td>'
            content += '<td><a href="/images/website/throughput_chart.png"><img class="screenshot" src="/images/website/throughput_chart.png" title="Throughput Chart"></a></td></tr></table></h1>'
            if not self.visitors_collection.find({'ip':cherrypy.request.remote.ip}).count():
                visitor_document = {'ip':         cherrypy.request.remote.ip,
                                    'useragent':  cherrypy.request.headers['User-Agent'],
                                    'lastaccess': datetime.datetime.utcnow()
                                   }
                self.visitors_collection.insert(visitor_document)
                content += '<div class="cookie"><p>We use cookies to ensure that we give you the best experience on our website. If you continue without changing your settings, we\'ll assume that you are happy to receive all cookies from this website. You are visiting from '+cherrypy.request.remote.ip+' ['+cherrypy.request.headers['User-Agent']+']</p></div>'
            else:
                for visitor_document in self.visitors_collection.find({'ip':cherrypy.request.remote.ip}):
                    visitor_document['lastaccess'] = datetime.datetime.utcnow()
                    self.visitors_collection.save(visitor_document)

            content += '<div class="flex-container">'

            content += '<div class="flex-item-large">'

            content += '<h2>Introduction</h2>'

            content += '<p align="justify">Welcome to Kanbanara, a mature, open-source, cross-platform Kanban-based project management system. It has been under constant development since the beginning of 2013.</p>'

            content += '<p align="justify">At Kanbanara\'s heart is its kanban board. This possesses a number of user-defined states/columns that cards pass through from inception to completion.</p>'

            content += '<p align="justify">Although Kanban was primarily developed for continuous delivery, Kanbanara has borrowed sprints, implemented as releases and iterations, from scrum.</p>'

            content += '<p align="justify">Saying Kanbanara has one Kanban board is a little simplistic as in fact the cards that get displayed in the columns can be in one of 58 different formats depending on which aspect of a card you wish to zero in on. The available card formats are Tabbed, Absenteeism, Activity, AffectsVersion, Attachments, Attributes, Avatar, Blockages, Bypass Reviews, Capitals, Children, Class Of Service, Comments, Co-Owner, Co-Reviewer, Cost, Creator, CRM Cases, Customer, Days In State, Deadline, Deferrals, Difficulty, Emotions, Escalation, Ext Ref, FixVersion, Focus, Hashtags, Identifier, Iteration, Last Changed, Last Touched, Lipsum, New, Owner Unassigned, Questions, Reassignments, Recidivism, Recurring, Release, Reopened, Resolution, Reviewer, Rules, Scope Creep, Search, Severity, Similars, Status, Subteam, Test Cases, Time, Today, Velocity, Votes, Yesterday and Internals.</p>'

            content += '<p align="justify">Where a card is missing an attribute so as to fail to display any useful information on a particular kanban board format, the card will still appear but as a placeholder, thereby reminding the team member to not forget about such a card!</p>'

            content += '<p align="justify">A card placed on the kanban board can be of any one of nine types - epic, feature, story, enhancement, defect, task, test, bug or transient. Such cards can be chained together with a simple parent-child relationship. Although a parent can have many children, a child can have only one parent.</p>'

            content += '<p align="justify">The most important cards in any state, those with critical or high priority, are automatically shown towards the top of the kanban board. Within each block grouping cards together of the same priority, such cards are additionally sorted by severity with the critical and high ones again towards the top. You can, of course, manually move them up, down, to the top or to the bottom within their respective group.</p>'

            content += '<p align="justify">To cope with its use on small screens or at standup, the font size of each card on the kanban board can be changed from minute through normal to huge.</p>'

            content += '<p align="justify">In addition to the kanban board itself, Kanbanara has also been developed around a global filter bar that allows you to select what cards actually get displayed on your kanban board, be they for a particular project, team member, release, iteration or card type, to mention just a subset of the available options. Should you see a number above a particular filter setting, this tells you how many cards are being filtered out as a result of this setting.</p>'

            content += '<p align="justify">Even though you have the full width of the screen at your disposal, the filter bar additionally allows you to select a range of states/columns to be displayed, allowing you, for example, to hide Backlog and Closed as required, thus giving more room to those columns containing cards you are actually interested in.</p>'

            content += '<p align="justify">Kanbanara features an expedite swim lane allowing you to expedite just one card per project at any one time.</p>'

            content += '<p align="justify">Kanbanara is SAAS-ready in supporting fully isolated projects. An individual user can be a team member in any number of projects and they will only ever be able to see those projects which they created or to which they have been invited.</p>'

            content += '<p align="justify">In addition to the Kanban board itself, your cards can be visualised in a number of ways such as list view, standup, backlog sorter, timeline, activity stream, diary, time sheet, wallboard, release kickoff and retrospective.</p>'

            content += '<p align="justify">Next action or deadline dates can be individually assigned to a card. The Today kanban board is particularly useful in isolating those cards requiring your attention today (or indeed yesterday). The Tomorrow kanban board is equally useful in highlighting those cards requiring attention tomorrow and during the next few days. Such cards can also be displayed per day on the Diary page.</p>'

            content += '<p align="justify">Kanbanara also supports a projection facility allowing you to visualise the kanban board upto six days into the future. Any cards currently blocked, deferred or hidden at the present time but will become unblocked, undeferred or unhidden by the day of the projected kanban board will be shown as such.</p>'

            content += '<p align="justify">Drag and drop is fully implemented on both the kanban board and the backlog sorter, allowing you to not only drag a card from one state to another, but also changing its priority, and in the case of the backlog sorter, a card\'s severity also, as required.</p>'

            content += '<p align="justify">Kanbanara features an extensive search facility that not only allows you to search within a particular card attribute but will also automatically ignore certain filter bar settings to maximise the chances of finding a match. There is even a search-specific kanban board that allows highlighting of the current search phrase, should a match be found, on any card displayed.</p>'

            content += '<p align="justify">Each card can be assigned up to two owners and up to two reviewers, thereby assisting with either pair programming or pseudo team members such as the current incumbent of the service support liason role.</p>'

            content += '<p align="justify">As all cards will eventually end up in the closed state one way or another, unless of course deleted, the number of cards displayed can be restricted to those cards closed in the last five years right down to those closed within the last hour.</p>'

            content += '<p align="justify">The filter bar makes it easy to switch between releases and/or iterations, be it the previous, current or next one.</p>'

            content += '<p align="justify">The settings for a given project can be restricted by allocating one of four roles - project manager, deputy project manager, team member or guest - to a given team member.</p>'

            content += '<p align="justify">Global Work-In-Progress limits can be assigned to a project, easily overridden by personal ones should a user wish. Certain states can be declared as buffer states and have special significance in Kanban. Should your Work-In-Progress limit for a particular state be reached, the system may automatically hold a card in a buffer state until spare capacity allows it to be moved forward. Your personal Work-In-Progress limits can be easily amended via the buttons at the top of each kanban board state column, either being set to a specific value or to unlimited. Should a state\'s Work-In-Progress limit be reached, a message to that effect will be shown between the cards at the appropriate point.</p>'

            content += '<p align="justify">Kanbanara supports both priority and severity, each to four levels, thus giving one of sixteen levels of urgency and importance to an individual card ranging from critical-critical, through high and medium, down to low-low.</p>'

            content += '<p align="justify">When assigning a reviewer to a card, the reviewer\'s names are placed in order of least busy to most busy, with a card count after their name, thereby allowing the least busy team member to be more likely chosen. When a card reaches the QA/Testing state, the owner and reviewer effectively switch roles, with the reviewer requested to work on the card and the owner a passive observer. As a reviewer, you can see what work is heading your way as such cards appear as ghost cards - greyed out in the states preceding Testing.</p>'

            content += '<p align="justify">Any card can be viewed in JSON format, not to mention a project itself, your session or your personal filter settings.</p>'

            content += '<p align="justify">With regard to metrics, Kanbanara possesses quite a few such charts - Abandoned, Bugs and Defects, Burndown Chart, Completed, Cumulative Flow Diagram, Division of Labour, Emotions, Recidivism Rate, Tags and Throughput.</p>'

            content += '<p align="justify">The Standup page features a cut down version of the kanban board and allows the person in charge to either step through each team member or each significant card in turn. Each card selected is then displayed towards the bottom of the screen, in both kanban board and list view formats, along with its children if any.</p>'

            content += '<p align="justify">A card can be blocked by a team member explicitly setting it as such. To such a block can be additionally added a date upon which the card will be automatically unblocked. A card can also be automatically blocked by its children if the parent and children get out of step as regards their states.</p>'

            content += '<p align="justify">Should a team member get absolutely stuck with a particular card, a stuck setting can be enabled by entering text describing how they are stuck and what assistance they require, whereupon such a card will appear on everyone else\'s kanban board in the same project thus allowing help from a colleague in the shortest possible time.</p>'

            content += '<p align="justify">Each team member has the ability to enter the next date range that they will be on holiday, thereby alerting their absence to the owners of cards to which they are a reviewer.</p>'

            content += '</div><div class="flex-item">'

            content += '<h2>Licence</h2>'
            content += '<p align="justify">Kanbanara has been open-sourced under the <a href="/admin/licence">GNU Affero General Public License version 3</a>.</p>'

            content += '<h2>Become a Member of the KSF</h2>'
            content += '<p align="justify">Why not <a href="/authenticate/register">become a member</a> of the <a href="/foundation">Kanbanara Software Foundation</a> and help us support Kanbanara into the future.</p>'

            content += '<h2>Experience Kanbanara Yourself</h2>'
            content += '<p align="justify">You can try out the full Kanbanara experience yourself <a href="/kanban">here</a>. You will need to register as a member of the Kanbanara Software Foundation first though!</p>'
            content += '<p align="justify">Should you wish to try it out without first registering, please feel free to <a href="/authenticate?username=guest@kanbanara.com&password=guest&returnurl=/kanban">log on</a> as username: \'guest@kanbanara.com\', password: \'guest\'.</p>'

            content += '<h2>Support</h2>'
            content += '<p align="justify">Should you discover a bug in Kanbanara or wish to suggest a new feature or enhance an existing one, please feel free to create a new card within the Kanbanara project within Kanbanara itself.</p>'
            content += '<p align="justify"><a href="/authenticate?username=support@kanbanara.com&password=support&returnurl=/kanban">Log on</a> as username: \'support@kanbanara.com\', password: \'support\'.</p>'

            content += '<h2>Limited WIP Society</h2>'
            content += '<p align="justify">Rebecca Shalfield is a member of the <a href="http://limitedwipsociety.ning.com/">Limited WIP Society</a>.</p>'

            content += '<h2>Contact Us</h2>'
            content += '<p align="justify">Should you have an idea for how Kanbanara can be enhanced, please don\'t hesitate to <a href="mailto:rebecca.shalfield@shalfield.com">contact us</a>.</p>'

            content += '</div></div>'

            content += '<p><br></p><p><br></p>'

            content += '</div>'
            content += '<div id="footer_container">'
            number_of_visitors = self.visitors_collection.find().count()
            content += '<div id="footer_contents"><h3>Copyright &copy; Kanbanara Software Foundation 2013-2017 | Number of Visitors: '+str(number_of_visitors)+'</h3></div>'
            content += '</div>'
            content += '</body></html>'
            return content
        else:
            raise cherrypy.HTTPRedirect("/kanban/index", 302)

    @cherrypy.expose
    def foundation(self):
        if (self.socket_hostname == 'www.kanbanara.com' or
                os.path.exists(self.current_dir+os.sep+'website')):
            with open(os.path.join(self.current_dir, 'html', 'foundation.html'), 'r') as handle:
                content = handle.read()

            return content
        else:
            raise cherrypy.HTTPRedirect("/kanban/index", 302)

    @cherrypy.expose
    def userguide(self):
        if self.socket_hostname == 'www.kanbanara.com' or os.path.exists(self.current_dir+os.sep+'website'):
            content = '<html><head>'
            content += '<title>Kanbanara - User Guide</title>'
            content += '<link rel="stylesheet" href="/css/website.css" type="text/css"><script type="text/javascript" src="/scripts/website.js"></script>'
            content += '<script type="text/javascript" src="/scripts/jquery-1.7.2.min.js"></script>'
            content += '</head>'
            content += '<body><div align="center">'
            content += '''<table width="100%" border="0">
                          <tr><td rowspan="2">
                          <table id="kanbanaralogo">
                          <tr><td class="red">K</td><td class="green">A</td><td class="blue">N</td></tr>
                          <tr><td class="blue">B</td><td class="red">A</td><td class="green">N</td></tr>
                          <tr><td class="green">A</td><td class="blue">R</td><td class="red">A</td></tr>
                          </table>
                          </td><td width="100%"><h1 class="header">Kanbanara</h1></td></tr><tr><td><h1 class="catchphrase">A Kanban-Based Project Management System</h1></td></tr>
                          </table>'''
            content += self.menubar('userguide')
            content += '<p><a href="/docs/build/html/index.html">HTML</a></p>'
            content += '<p><a href="/docs/build/epub/index.html">EPUB</a></p>'

            content += '</div>'
            content += '<div id="footer_container">'
            content += '<div id="footer_contents"><h3>Copyright &copy; Kanbanara Software Foundation 2013-2017</h3></div>'
            content += '</div>'
            content += '</body></html>'
            return content
        else:
            raise cherrypy.HTTPRedirect("/kanban/index", 302)

    @cherrypy.expose
    def wiki(self):
        if (self.socket_hostname == 'www.kanbanara.com' or
                os.path.exists(self.current_dir+os.sep+'website')):
            with open(self.current_dir+os.sep+'html'+os.sep+'wiki.html', 'r') as handle:
                content = handle.read()

            return content
        else:
            raise cherrypy.HTTPRedirect("/kanban/index", 302)

    @cherrypy.expose
    def forum(self):
        if (self.socket_hostname == 'www.kanbanara.com' or
                os.path.exists(self.current_dir+os.sep+'website')):
            with open(self.current_dir+os.sep+'html'+os.sep+'forum.html', 'r') as handle:
                content = handle.read()

            return content
        else:
            raise cherrypy.HTTPRedirect("/kanban/index", 302)

    @cherrypy.expose
    def slideshow(self):
        if (self.socket_hostname == 'www.kanbanara.com' or
                os.path.exists(self.current_dir+os.sep+'website')):
            with open(self.current_dir+os.sep+'html'+os.sep+'slideshow.html', 'r') as handle:
                content = handle.read()

            return content
        else:
            raise cherrypy.HTTPRedirect("/kanban/index", 302)

    @cherrypy.expose
    def downloads(self):
        if (self.socket_hostname == 'www.kanbanara.com' or
                os.path.exists(self.current_dir+os.sep+'website')):
            with open(os.path.join(self.current_dir, 'html', 'downloads.html'), 'r') as handle:
                content = handle.read()

            return content
        else:
            raise cherrypy.HTTPRedirect("/kanban/index", 302)

    @cherrypy.expose
    def features(self):
        if (self.socket_hostname == 'www.kanbanara.com' or
                os.path.exists(self.current_dir+os.sep+'website')):
            with open(self.current_dir+os.sep+'html'+os.sep+'features.html', 'r') as handle:
                content = handle.read()

            return content
        else:
            raise cherrypy.HTTPRedirect("/kanban/index", 302)

    @cherrypy.expose
    def contactus(self):
        if self.socket_hostname == 'www.kanbanara.com' or os.path.exists(self.current_dir+os.sep+'website'):
            content = '<html><head>'
            content += '<title>Kanbanara</title>'
            content += '<link rel="stylesheet" href="/css/website.css" type="text/css"><script type="text/javascript" src="/scripts/website.js"></script>'
            content += '<script type="text/javascript" src="/scripts/jquery-1.7.2.min.js"></script>'
            content += '</head>'
            content += '<body><div align="center">'
            content += '''<table width="100%" border="0">
                          <tr><td rowspan="2">
                          <table id="kanbanaralogo">
                          <tr><td class="red">K</td><td class="green">A</td><td class="blue">N</td></tr>
                          <tr><td class="blue">B</td><td class="red">A</td><td class="green">N</td></tr>
                          <tr><td class="green">A</td><td class="blue">R</td><td class="red">A</td></tr>
                          </table>
                          </td><td width="100%"><h1 class="header">Kanbanara</h1></td></tr><tr><td><h1 class="catchphrase">A Kanban-Based Project Management System</h1></td></tr>
                          </table>'''
            content += self.menubar('contactus')
            content += '<p align="justify">Should you wish to make a comment or ask us a question, please don\'t hesitate to fill in and email us the following form...</p>'

            content += '''<form action="mailto:rebecca.shalfield@shalfield.com" method="post" enctype="text/plain"><table>
            <tr><td>Name:</td><td><input type="text" name="name" size="40"></td></tr>
            <tr><td>Email:</td><td><input type="text" name="email" size="40"></td></tr>
            <tr><td>Comment:</td><td><textarea name="comment" cols="40" rows="5"></textarea></td></tr>
            <tr><td colspan="2" align="center"><input type="submit" value="Send"></td></tr>
            </table></form>'''

            content += '</div>'
            content += '<div id="footer_container">'
            content += '<div id="footer_contents"><h3>Copyright &copy; Kanbanara Software Foundation 2013-2017</h3></div>'
            content += '</div>'
            content += '</body></html>'
            return content
        else:
            raise cherrypy.HTTPRedirect("/kanban/index", 302)

    def app_table(self):
        content = []
        known_apps = [('kanbanara', 'Kanbanara', '/kanban/', 'A Kanban-Based Project Management System')
                     ]

        for key in visible_components.keys():
            if visible_components[key] == ('','','','',''):
                known = False
                for known_app in known_apps:
                    if known_app[0] == key:
                        known = True
                        visible_components[key] = (known_app[1], known_app[2], known_app[3], "", 'disabled')
                        break

                if not known:
                    del visible_components[key]

        sorted_visible_components = list(visible_components.keys())
        sorted_visible_components.sort()
        content.append('<table border="0" cellpadding="3" cellspacing="3"><tr>')
        counter = 0
        for sorted_visible_component in sorted_visible_components:
            (title,urlpath,description,copyright,state) = visible_components[sorted_visible_component]
            content.append('<td>')
            if state in ['disabled', 'inactive']:
                content.append('<div class="inactive">')
                content.append('<h2>'+title+' [Inactive]</h2>')
            else:
                content.append('<div class="active">')
                content.append('<h2><a href="'+urlpath+'">'+title+'</a></h2>')

            content.append('<h4>'+description+'</h4>')
            if copyright:
                content.append('<p>'+copyright+'</p>')

            content.append('</div></td>')
            counter += 1
            if counter == 4:
                content.append('</tr><tr>')
                counter = 0

        content.append('</tr>')
        content.append('</table>')
        return "".join(content)

    def error_page_400(self, status, message, traceback, version):
        self.write_errors_log_message(status, message, traceback)
        content = []
        content.append(f'<html><head><title>{status}</title><link rel="stylesheet" href="/css/rootmenu.css" type="text/css"><script type="text/javascript" src="/scripts/website.js"></script><meta name="robots" content="noindex" /><meta name="robots" content="nofollow" /></head><body>')
        content.append('<div align="center">')
        content.append('<h1 class="header">Kanbanara</h1>')
        content.append("<h2>400 Bad Request</h2>")
        content.append(f'<h3>{message}</h3>')
        content.append('<form action="/kanban/index" method="post"><input class="button" type="submit" value="Return to Kanbanara"></form>')
        content.append(f'<pre>{traceback}</pre>')
        content.append('</div>')
        content.append('<div id="footer_container">')
        content.append('<div id="footer_contents"><h3>Copyright &copy; Kanbanara Software Foundation 2013-2017</h3></div>')
        content.append('</div>')
        content.append('<p><br></p>')
        content.append('</body></html>')
        return "".join(content)

    def error_page_401(self, status, message, traceback, version):
        self.write_errors_log_message(status, message, traceback)
        content = []
        content.append('<html><head><title>'+status+'</title><link rel="stylesheet" href="/css/rootmenu.css" type="text/css"><script type="text/javascript" src="/scripts/website.js"></script><meta name="robots" content="noindex" /><meta name="robots" content="nofollow" /></head><body>')
        content.append('<div align="center">')
        content.append('<h1 class="header">Kanbanara</h1>')
        content.append("<h2>401 Unauthorised</h2>")
        content.append("<h2>You are not authorised to access this resource</h2>")
        content.append(f'<h3>{message}</h3>')
        content.append('<form action="/kanban/index" method="post"><input class="button" type="submit" value="Return to Kanbanara"></form>')
        content.append(f'<pre>{traceback}</pre>')
        content.append('</div>')
        content.append('<div id="footer_container">')
        content.append('<div id="footer_contents"><h3>Copyright &copy; Kanbanara Software Foundation 2013-2017</h3></div>')
        content.append('</div>')
        content.append('<p><br></p>')
        content.append('</body></html>')
        return "".join(content)

    def error_page_403(self, status, message, traceback, version):
        self.write_errors_log_message(status, message, traceback)
        content = []
        content.append('<html><head><title>'+status+'</title><link rel="stylesheet" href="/css/rootmenu.css" type="text/css"><script type="text/javascript" src="/scripts/website.js"></script><meta name="robots" content="noindex" /><meta name="robots" content="nofollow" /></head><body>')
        content.append('<div align="center">')
        content.append('<h1 class="header">Kanbanara</h1>')
        content.append("<h2>403 Forbidden</h2>")
        content.append("<h2>You are not allowed to access this resource</h2>")
        content.append(f'<h3>{message}</h3>')
        content.append('<form action="/kanban/index" method="post"><input class="button" type="submit" value="Return to Kanbanara"></form>')
        content.append(f'<pre>{traceback}</pre>')
        content.append('</div>')
        content.append('<div id="footer_container">')
        content.append('<div id="footer_contents"><h3>Copyright &copy; Kanbanara Software Foundation 2013-2017</h3></div>')
        content.append('</div>')
        content.append('<p><br></p>')
        content.append('</body></html>')
        return "".join(content)

    def error_page_404(self, status, message, traceback, version):
        self.write_errors_log_message(status, message, traceback)
        content = []
        content.append('<html><head><title>'+status+'</title><link rel="stylesheet" href="/css/rootmenu.css" type="text/css"><script type="text/javascript" src="/scripts/website.js"></script><meta name="robots" content="noindex" /><meta name="robots" content="nofollow" /></head><body>')
        content.append('<div align="center">')
        content.append('<h1 class="header">Kanbanara</h1>')
        content.append("<h2>404 Not Found</h2>")
        content.append("<h2>Sorry, we're unable to locate that page!</h2>")
        content.append('<h3>'+message+'</h3>')
        content.append('<form action="/kanban/index" method="post"><input class="button" type="submit" value="Return to Kanbanara"></form>')
        content.append('<pre>'+traceback+'</pre>')
        content.append('</div>')
        content.append('<div id="footer_container">')
        content.append('<div id="footer_contents"><h3>Copyright &copy; Kanbanara Software Foundation 2013-2017</h3></div>')
        content.append('</div>')
        content.append('<p><br></p>')
        content.append('</body></html>')
        return "".join(content)

    def error_page_500(self, status, message, traceback, version):
        self.write_errors_log_message(status, message, traceback)
        content = []
        content.append('<html><head><title>'+status+'</title><link rel="stylesheet" href="/css/rootmenu.css" type="text/css"><script type="text/javascript" src="/scripts/website.js"></script><meta name="robots" content="noindex" /><meta name="robots" content="nofollow" /></head><body>')
        content.append('<div align="center">')
        content.append('<h1 class="header">Kanbanara</h1>')
        content.append("<h2>500 Internal Server Error</h2>")
        content.append(f'<h3>{message}</h3>')
        content.append('<form action="/kanban/index" method="post"><input class="button" type="submit" value="Return to Kanbanara"></form>')
        content.append(f'<pre>{traceback}</pre>')
        content.append('</div>')
        content.append('<div id="footer_container">')
        content.append('<div id="footer_contents"><h3>Copyright &copy; Kanbanara Software Foundation 2013-2017</h3></div>')
        content.append('</div>')
        content.append('<p><br></p>')
        content.append('</body></html>')
        return "".join(content)
        
    def write_errors_log_message(self, status, message, traceback):
        epoch = datetime.datetime.utcnow()
        todays_date = "%4d%02d%02d" % (epoch.year, epoch.month, epoch.day)
        with open(os.path.join(self.current_dir, 'logs', f'errors_{todays_date}.log'), 'a') as handle:
            handle.write(time.ctime()+'\n')
            handle.write(status+'\n')
            handle.write(message+'\n')
            handle.write(traceback+'\n\n')

def write_log_message(handle, message):
    handle.write(message+'\n')
    print(message)

def read_webapp_rootmenu_ini_file_for_main(pathname, object):
    """ Read component.ini file """
    state = ""
    visibility = ""
    title = object.capitalize()
    urlpath = '/'+object+'/'
    description = object.capitalize()
    copyright = ""
    with open(pathname+os.sep+object+os.sep+"component.ini", "r") as handle:
        settings = handle.read()

    state_pattern = re.compile(r'(?i)state\s?=\s?(\S+)')
    results = state_pattern.findall(settings)
    if results != []:
        state = results[0]

    visibility_pattern = re.compile(r'(?i)visibility\s?=\s?(\S+)')
    results = visibility_pattern.findall(settings)
    if results != []:
        visibility = results[0]

    title_pattern = re.compile(r'(?i)title\s?=\s?([\S ]+)')
    results = title_pattern.findall(settings)
    if results != []:
        title = results[0]

    urlpath_pattern = re.compile(r'(?i)urlpath\s?=\s?(\S+)')
    results = urlpath_pattern.findall(settings)
    if results != []:
        urlpath = results[0]

    description_pattern = re.compile(r'(?i)description\s?=\s?([\S ]+)')
    results = description_pattern.findall(settings)
    if results != []:
        description = results[0]

    copyright_pattern = re.compile(r'(?i)copyright\s?=\s?([\S ]+)')
    results = copyright_pattern.findall(settings)
    if results != []:
        copyright = results[0]

    return state, visibility, title, urlpath, description, copyright

def define_conf(objects):
    conf = {}
    conf['/'] = {'tools.staticdir.root': CURRENT_DIR}
    for directory in ['attachments', 'css', 'docs', 'downloads', 'font-awesome', 'images', 'jquery-ui-1.12.1.custom',
                      'licences', 'manuals', 'scripts', 'svgcharts']:
        if os.path.exists(CURRENT_DIR+os.sep+directory):
            conf['/'+directory] = {'tools.staticdir.on':  True,
                                   'tools.staticdir.dir': directory}

    return conf
    
def create_default_ini_files():
    def create_random_string(length):
        random_string = "".join([chr(random.randint(97, 122)) for i in range(length)])
        return random_string

    if not os.path.exists(os.path.join(CURRENT_DIR, 'administrator.ini')):
        with open(os.path.join(CURRENT_DIR, 'administrator.ini'), 'w') as handle:
            handle.write(f'username={create_random_string(10)}\n')
            handle.write(f'password={create_random_string(10)}')
            handle.write('smtp_server_host=\n')
            handle.write('smtp_server_password=\n')
            handle.write('smtp_server_port=\n')
            handle.write('smtp_server_username=\n')
            handle.write('email_address=\n')

    if not os.path.exists(os.path.join(CURRENT_DIR, 'kanbanara.ini')):
        with open(os.path.join(CURRENT_DIR, 'kanbanara.ini'), 'w') as handle:
            handle.write('instance=Untitled\n')
            handle.write('port=80\n')
            handle.write('request_queue_size=10\n')
            handle.write('socket_queue_size=30\n')
            handle.write('thread_pool=50')
            
    if not os.path.exists(os.path.join(CURRENT_DIR, 'mongodb.ini')):
        with open(os.path.join(CURRENT_DIR, 'mongodb.ini'), 'w') as handle:
            handle.write('host=localhost\n')
            handle.write('port=27017\n')
            handle.write('username=\n')
            handle.write('password=\n')
            handle.write('bindir=C:\Program Files\MongoDB\bin')

def read_kanbanara_ini_file():
    """Reads the kanbanara.ini file and extracts port information"""
    port = ''
    request_queue_size = 10
    socket_queue_size = 30
    thread_pool = 50
    with open(os.path.join(CURRENT_DIR, 'kanbanara.ini'), 'r') as handle:
        settings = handle.read()

        port_pattern = re.compile(r'(?i)[^_]port\s?=\s?(\d+)')
        results = port_pattern.findall(settings)
        if results:
            port = results[0]

        request_queue_size_pattern = re.compile(r'(?i)request_queue_size\s?=\s?(\d+)')
        results = request_queue_size_pattern.findall(settings)
        if results:
            request_queue_size = int(results[0])

        socket_queue_size_pattern = re.compile(r'(?i)socket_queue_size\s?=\s?(\d+)')
        results = socket_queue_size_pattern.findall(settings)
        if results:
            socket_queue_size = int(results[0])

        thread_pool_pattern = re.compile(r'(?i)thread_pool\s?=\s?(\d+)')
        results = thread_pool_pattern.findall(settings)
        if results:
            thread_pool = int(results[0])

    return port, request_queue_size, socket_queue_size, thread_pool

def get_port():
    '''Get port from directory, and if not available, from kanbanara.ini file'''
    port = ''
    pattern = re.compile(r'(?i)Kanbanara(\d+)')
    results = pattern.findall(CURRENT_DIR)
    if results != []:
        port = results[0]

    if not port:
        port, _, _, _ = read_kanbanara_ini_file()

    if not port:
        port = '80'

    return port

def start_standalone():
    conf = define_conf(objects)
    cherrypy.tree.mount(Root(), '/', config=conf)
    if os.path.exists(CURRENT_DIR+os.sep+'loopback'):
        ip_addr = '127.0.0.1'
    else:
        ip_addr = socket.gethostbyname_ex(SOCKET_HOSTNAME)[2][0]
        
    port = get_port()

    _, request_queue_size, socket_queue_size, thread_pool = read_kanbanara_ini_file()

    global_conf = {'global': {'autoreload.frequency':      5,
                              'autoreload.on':             True,
                              'server.socket_host':        ip_addr,
                              'server.socket_port':        int(port),
                              'server.request_queue_size': request_queue_size,
                              'server.socket_queue_size':  socket_queue_size,
                              'server.thread_pool':        thread_pool
                             }
                  }
    cherrypy.config.update(global_conf)
    cherrypy.engine.start()
    if os.path.exists('shellexe.exe'):
        os.system('shellexe.exe http://'+ip_addr+':'+port+'/')

SOCKET_HOSTNAME = socket.gethostname()
pathname = os.path.dirname(sys.argv[0])
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
create_default_ini_files()
objects = os.listdir(os.path.abspath(pathname))
visible_components = {}
for directory in ['attachments', 'dbdump', 'downloads', 'logs']:
    if not os.path.exists(os.path.join(CURRENT_DIR, directory)):
        os.mkdir(os.path.join(CURRENT_DIR, directory))

with open(CURRENT_DIR+os.sep+'logs'+os.sep+'start.log', 'w') as handle:
    for component_name in objects:
        if os.path.isdir(pathname+os.sep+component_name):
            component_path = pathname+os.sep+component_name
            if os.path.exists(component_path+os.sep+'component.ini'):
                state, visibility, title, urlpath, description, copyright = read_webapp_rootmenu_ini_file_for_main(pathname, component_name)
                if state in ['enabled', 'active']:
                    start_time = time.mktime(time.localtime())
                    if (os.path.exists(component_path+os.sep+component_name+'.pyc') or
                            os.path.exists(component_path+os.sep+component_name+'.py')):
                        sys.path.append(component_path)
                        __import__(component_name+'.'+component_name)
                    elif os.path.exists(component_path+os.sep+'index.html'):
                        True

                    if visibility in ['visible']:
                        visible_components[component_name] = (title, urlpath, description,
                                                              copyright, state)

                    end_time = time.mktime(time.localtime())
                    write_log_message(handle, "Component "+component_name+" initialised in "+str(end_time-start_time)+' seconds')
                elif state in ['disabled', 'inactive']:
                    if visibility in ['visible']:
                        visible_components[component_name] = (title, urlpath, description,
                                                              copyright, state)

if __name__ == '__main__':
    start_standalone()
