diff --git a/LICENSE.txt b/LICENSE.txt
index 2c095c8..d159169 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,274 +1,339 @@
-GNU GENERAL PUBLIC LICENSE
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
 
-              Version 2, June 1991
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
 
-Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave,
-Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute
-verbatim copies of this license document, but changing it is not allowed.
+                            Preamble
 
-                  Preamble
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
 
-The licenses for most software are designed to take away your freedom to
-share and change it. By contrast, the GNU General Public License is
-intended to guarantee your freedom to share and change free software--to
-make sure the software is free for all its users. This General Public License
-applies to most of the Free Software Foundation's software and to any other
-program whose authors commit to using it. (Some other Free Software
-Foundation software is covered by the GNU Library General Public License
-instead.) You can apply it to your programs, too.
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
 
-When we speak of free software, we are referring to freedom, not price. Our
-General Public Licenses are designed to make sure that you have the
-freedom to distribute copies of free software (and charge for this service if
-you wish), that you receive source code or can get it if you want it, that you
-can change the software or use pieces of it in new free programs; and that
-you know you can do these things.
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
 
-To protect your rights, we need to make restrictions that forbid anyone to
-deny you these rights or to ask you to surrender the rights. These restrictions
-translate to certain responsibilities for you if you distribute copies of the
-software, or if you modify it.
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
 
-For example, if you distribute copies of such a program, whether gratis or for
-a fee, you must give the recipients all the rights that you have. You must make
-sure that they, too, receive or can get the source code. And you must show
-them these terms so they know their rights.
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
 
-We protect your rights with two steps: (1) copyright the software, and (2)
-offer you this license which gives you legal permission to copy, distribute
-and/or modify the software.
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
 
-Also, for each author's protection and ours, we want to make certain that
-everyone understands that there is no warranty for this free software. If the
-software is modified by someone else and passed on, we want its recipients
-to know that what they have is not the original, so that any problems
-introduced by others will not reflect on the original authors' reputations.
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
 
-Finally, any free program is threatened constantly by software patents. We
-wish to avoid the danger that redistributors of a free program will individually
-obtain patent licenses, in effect making the program proprietary. To prevent
-this, we have made it clear that any patent must be licensed for everyone's
-free use or not licensed at all.
+  The precise terms and conditions for copying, distribution and
+modification follow.
 
-The precise terms and conditions for copying, distribution and modification
-follow.
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 
-           GNU GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND
-               MODIFICATION
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
 
-0. This License applies to any program or other work which contains a notice
-placed by the copyright holder saying it may be distributed under the terms
-of this General Public License. The "Program", below, refers to any such
-program or work, and a "work based on the Program" means either the
-Program or any derivative work under copyright law: that is to say, a work
-containing the Program or a portion of it, either verbatim or with
-modifications and/or translated into another language. (Hereinafter, translation
-is included without limitation in the term "modification".) Each licensee is
-addressed as "you".
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
 
-Activities other than copying, distribution and modification are not covered
-by this License; they are outside its scope. The act of running the Program is
-not restricted, and the output from the Program is covered only if its contents
-constitute a work based on the Program (independent of having been made
-by running the Program). Whether that is true depends on what the Program
-does.
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
 
-1. You may copy and distribute verbatim copies of the Program's source
-code as you receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice and
-disclaimer of warranty; keep intact all the notices that refer to this License
-and to the absence of any warranty; and give any other recipients of the
-Program a copy of this License along with the Program.
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
 
-You may charge a fee for the physical act of transferring a copy, and you
-may at your option offer warranty protection in exchange for a fee.
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
 
-2. You may modify your copy or copies of the Program or any portion of it,
-thus forming a work based on the Program, and copy and distribute such
-modifications or work under the terms of Section 1 above, provided that you
-also meet all of these conditions:
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
 
-a) You must cause the modified files to carry prominent notices stating that
-you changed the files and the date of any change.
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
 
-b) You must cause any work that you distribute or publish, that in whole or in
-part contains or is derived from the Program or any part thereof, to be
-licensed as a whole at no charge to all third parties under the terms of this
-License.
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
 
-c) If the modified program normally reads commands interactively when run,
-you must cause it, when started running for such interactive use in the most
-ordinary way, to print or display an announcement including an appropriate
-copyright notice and a notice that there is no warranty (or else, saying that
-you provide a warranty) and that users may redistribute the program under
-these conditions, and telling the user how to view a copy of this License.
-(Exception: if the Program itself is interactive but does not normally print such
-an announcement, your work based on the Program is not required to print
-an announcement.)
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
 
-These requirements apply to the modified work as a whole. If identifiable
-sections of that work are not derived from the Program, and can be
-reasonably considered independent and separate works in themselves, then
-this License, and its terms, do not apply to those sections when you distribute
-them as separate works. But when you distribute the same sections as part
-of a whole which is a work based on the Program, the distribution of the
-whole must be on the terms of this License, whose permissions for other
-licensees extend to the entire whole, and thus to each and every part
-regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest your rights to
-work written entirely by you; rather, the intent is to exercise the right to
-control the distribution of derivative or collective works based on the
-Program.
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
 
 In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of a
-storage or distribution medium does not bring the other work under the scope
-of this License.
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
 
-3. You may copy and distribute the Program (or a work based on it, under
-Section 2) in object code or executable form under the terms of Sections 1
-and 2 above provided that you also do one of the following:
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
 
-a) Accompany it with the complete corresponding machine-readable source
-code, which must be distributed under the terms of Sections 1 and 2 above
-on a medium customarily used for software interchange; or,
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
 
-b) Accompany it with a written offer, valid for at least three years, to give
-any third party, for a charge no more than your cost of physically performing
-source distribution, a complete machine-readable copy of the corresponding
-source code, to be distributed under the terms of Sections 1 and 2 above on
-a medium customarily used for software interchange; or,
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
 
-c) Accompany it with the information you received as to the offer to distribute
-corresponding source code. (This alternative is allowed only for
-noncommercial distribution and only if you received the program in object
-code or executable form with such an offer, in accord with Subsection b
-above.)
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
 
 The source code for a work means the preferred form of the work for
-making modifications to it. For an executable work, complete source code
-means all the source code for all modules it contains, plus any associated
-interface definition files, plus the scripts used to control compilation and
-installation of the executable. However, as a special exception, the source
-code distributed need not include anything that is normally distributed (in
-either source or binary form) with the major components (compiler, kernel,
-and so on) of the operating system on which the executable runs, unless that
-component itself accompanies the executable.
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
 
-If distribution of executable or object code is made by offering access to
-copy from a designated place, then offering equivalent access to copy the
-source code from the same place counts as distribution of the source code,
-even though third parties are not compelled to copy the source along with the
-object code.
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
 
-4. You may not copy, modify, sublicense, or distribute the Program except as
-expressly provided under this License. Any attempt otherwise to copy,
-modify, sublicense or distribute the Program is void, and will automatically
-terminate your rights under this License. However, parties who have received
-copies, or rights, from you under this License will not have their licenses
-terminated so long as such parties remain in full compliance.
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
 
-5. You are not required to accept this License, since you have not signed it.
-However, nothing else grants you permission to modify or distribute the
-Program or its derivative works. These actions are prohibited by law if you
-do not accept this License. Therefore, by modifying or distributing the
-Program (or any work based on the Program), you indicate your acceptance
-of this License to do so, and all its terms and conditions for copying,
-distributing or modifying the Program or works based on it.
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
 
-6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the original
-licensor to copy, distribute or modify the Program subject to these terms and
-conditions. You may not impose any further restrictions on the recipients'
-exercise of the rights granted herein. You are not responsible for enforcing
-compliance by third parties to this License.
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
 
-7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues), conditions
-are imposed on you (whether by court order, agreement or otherwise) that
-contradict the conditions of this License, they do not excuse you from the
-conditions of this License. If you cannot distribute so as to satisfy
-simultaneously your obligations under this License and any other pertinent
-obligations, then as a consequence you may not distribute the Program at all.
-For example, if a patent license would not permit royalty-free redistribution
-of the Program by all those who receive copies directly or indirectly through
-you, then the only way you could satisfy both it and this License would be to
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
 refrain entirely from distribution of the Program.
 
-If any portion of this section is held invalid or unenforceable under any
-particular circumstance, the balance of the section is intended to apply and
-the section as a whole is intended to apply in other circumstances.
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
 
-It is not the purpose of this section to induce you to infringe any patents or
-other property right claims or to contest validity of any such claims; this
-section has the sole purpose of protecting the integrity of the free software
-distribution system, which is implemented by public license practices. Many
-people have made generous contributions to the wide range of software
-distributed through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing to
-distribute software through any other system and a licensee cannot impose
-that choice.
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
 
-This section is intended to make thoroughly clear what is believed to be a
-consequence of the rest of this License.
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
 
-8. If the distribution and/or use of the Program is restricted in certain
-countries either by patents or by copyrighted interfaces, the original copyright
-holder who places the Program under this License may add an explicit
-geographical distribution limitation excluding those countries, so that
-distribution is permitted only in or among countries not thus excluded. In such
-case, this License incorporates the limitation as if written in the body of this
-License.
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
 
-9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time. Such new versions will be
-similar in spirit to the present version, but may differ in detail to address new
-problems or concerns.
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
 
-Each version is given a distinguishing version number. If the Program specifies
-a version number of this License which applies to it and "any later version",
-you have the option of following the terms and conditions either of that
-version or of any later version published by the Free Software Foundation. If
-the Program does not specify a version number of this License, you may
-choose any version ever published by the Free Software Foundation.
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
 
-10. If you wish to incorporate parts of the Program into other free programs
-whose distribution conditions are different, write to the author to ask for
-permission. For software which is copyrighted by the Free Software
-Foundation, write to the Free Software Foundation; we sometimes make
-exceptions for this. Our decision will be guided by the two goals of
-preserving the free status of all derivatives of our free software and of
-promoting the sharing and reuse of software generally.
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
 
-               NO WARRANTY
+                            NO WARRANTY
 
-11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE,
-THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT
-PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
-STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
-OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
-WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
-INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
-PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
-NECESSARY SERVICING, REPAIR OR CORRECTION.
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
 
-12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR
-AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR
-ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE
-LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL,
-SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
-ARISING OUT OF THE USE OR INABILITY TO USE THE
-PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
-OR DATA BEING RENDERED INACCURATE OR LOSSES
-SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE
-PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN
-IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF
-THE POSSIBILITY OF SUCH DAMAGES.
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
 
-          END OF TERMS AND CONDITIONS
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/rc4.inc b/rc4.inc
deleted file mode 100644
index 37bf9ed..0000000
--- a/rc4.inc
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-
-/**
- * @file
- * RC4 symmetric cipher encryption/decryption
- * Copyright (c) 2006 by Ali Farhadi.
- * released under the terms of the Gnu Public License.
- * see the GPL for details.
- *
- * Email: ali[at]farhadi[dot]ir
- * Website: http://farhadi.ir/
- */
-
-/**
- * Encrypt given plain text using the key with RC4 algorithm.
- * All parameters and return value are in binary format.
- *
- * @param string key - secret key for encryption
- * @param string pt - plain text to be encrypted
- * @return string
- */
-function rc4Encrypt($key, $pt) {
-	$s = array();
-	for ($i=0; $i<256; $i++) {
-		$s[$i] = $i;
-	}
-	$j = 0;
-	$x;
-	for ($i=0; $i<256; $i++) {
-		$j = ($j + $s[$i] + ord($key[$i % strlen($key)])) % 256;
-		$x = $s[$i];
-		$s[$i] = $s[$j];
-		$s[$j] = $x;
-	}
-	$i = 0;
-	$j = 0;
-	$ct = '';
-	$y;
-	for ($y=0; $y<strlen($pt); $y++) {
-		$i = ($i + 1) % 256;
-		$j = ($j + $s[$i]) % 256;
-		$x = $s[$i];
-		$s[$i] = $s[$j];
-		$s[$j] = $x;
-		$ct .= $pt[$y] ^ chr($s[($s[$i] + $s[$j]) % 256]);
-	}
-	return $ct;
-}
-
-/**
- * Decrypt given cipher text using the key with RC4 algorithm.
- * All parameters and return value are in binary format.
- *
- * @param string key - secret key for decryption
- * @param string ct - cipher text to be decrypted
- * @return string
-*/
-
-function rc4Decrypt($key, $ct) {
-	return rc4Encrypt($key, $ct);
-}
\ No newline at end of file
diff --git a/simplenews_statistics.info b/simplenews_statistics.info
index 9d5e7eb..d441b29 100644
--- a/simplenews_statistics.info
+++ b/simplenews_statistics.info
@@ -1,12 +1,7 @@
-;hacked by Fedik http://drupal.org/user/1324974
 name = "Simplenews Statistics"
 description = "Gathers newsletter statistics"
-package = Mail
-
 dependencies[] = simplenews
-;dependencies[] = views
-
-
-version = "7.x-1.x-dev"
-core = "7.x"
-project = "simplenews_statistics"
+dependencies[] = views
+configure = admin/config/simplenews/statistics
+package = Mail
+core = 7.x
diff --git a/simplenews_statistics.install b/simplenews_statistics.install
index aee081c..b8a07ae 100644
--- a/simplenews_statistics.install
+++ b/simplenews_statistics.install
@@ -2,24 +2,11 @@
 
 /**
  * @file
- * Simplenews Statistics installation.
+ * Simplenews statistics (un)install and updates file.
  */
-function simplenews_statistics_install(){
-	//get alredy sended
-	$result = db_select('simplenews_newsletter','s')
-		->condition('status','1','>=')
-		->fields('s',array('nid'))
-		->execute()
-		->fetchCol();
-	//add alredy sended to own table
-	$query = db_insert('simplenews_statistics')->fields(array('nid'));
-	foreach($result as $nid){
-		$query->values(array('nid'=>$nid));
-	}
-	$query->execute();
-}
+
 /**
- * Implementation of hook_schema().
+ * Implements hook_schema().
  */
 function simplenews_statistics_schema() {
   $schema['simplenews_statistics'] = array(
@@ -38,34 +25,42 @@
         'not null' => TRUE,
         'default' => 0,
       ),
-      'clicks' => array(
+      'total_clicks' => array(
         'description' => 'Total clicks for this newsletter',
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0,
       ),
-      'opens' => array(
+      'total_opens' => array(
         'description' => 'Total opens for this newsletter',
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0,
       ),
+      'unique_opens' => array(
+        'description' => 'Emailaddress-unique opens for this newsletter',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'user_unique_click_through' => array(
+        'description' => 'Number of users who have clicked at least one link in the newsletter',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
     ),
-  	'foreign keys' => array(
-        'nid' => array(
-          'table' => 'node',
-          'columns' => array('nid' => 'nid'),
- 		),
-  	),
     'primary key' => array('nid'),
   );
 
   $schema['simplenews_statistics_clicks'] = array(
     'description' => 'Newsletter Clicks',
     'fields' => array(
-       'email' => array(
+      'email' => array(
         'description' => 'Primary key: Email.',
         'type' => 'varchar',
         'not null' => TRUE,
@@ -93,13 +88,6 @@
         'default' => 0,
       ),
     ),
-  	'primary key' => array('timestamp'),
-  	'foreign keys' => array(
-        'nid' => array(
-            'table' => 'node',
-            'columns' => array('nid' => 'nid'),
-  		),
-  	),
   );
 
   $schema['simplenews_statistics_opens'] = array(
@@ -118,13 +106,6 @@
         'not null' => TRUE,
         'default' => 0,
       ),
-      'opens' => array(
-        'description' => 'Amount of opens',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
       'timestamp' => array(
         'description' => 'Time of view',
         'type' => 'int',
@@ -133,14 +114,28 @@
         'default' => 0,
       ),
     ),
-  	'primary key' => array('timestamp'),
-  	'foreign keys' => array(
-        'nid' => array(
-           'table' => 'node',
-           'columns' => array('nid' => 'nid'),
-  		),
-  	),
   );
   return $schema;
 }
 
+/**
+ * Implements hook_install().
+ */
+function simplenews_statistics_install() {
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function simplenews_statistics_uninstall() {
+  variable_del('simplenews_statistics_ga_utm_source');
+  variable_del('simplenews_statistics_ga_utm_medium');
+  variable_del('simplenews_statistics_ga_utm_campaign');
+}
+
+/**
+ * #7000: 6.x-3.x to 7.x-1.x upgrade.
+ */
+function simplenews_statistics_update_7000() {
+  //@todo
+}
diff --git a/simplenews_statistics.module b/simplenews_statistics.module
index 22b8cbc..47ef947 100644
--- a/simplenews_statistics.module
+++ b/simplenews_statistics.module
@@ -2,27 +2,28 @@
 
 /**
  * @file
- * Gathers newsletter statistics.
+ * Main simplenews statistics file.
  */
- 
+
 /**
- * Implementation of hook_menu().
+ * Implements hook_menu().
  */
 function simplenews_statistics_menu() {
-//   $items['admin/content/simplenews/statistics'] = array(
-//     'title' => 'Statistics',
-//     'description' => 'View the openings/rate and clicks/CTR for your newsletters',
-//     'type' => MENU_LOCAL_ACTION,
-//     'page callback' => 'drupal_get_form',
-//     'page arguments' => array('simplenews_statistics_admin_form'),
-//     'access arguments' => array('administer newsletters'),
-//   );
+  $items['admin/content/simplenews/statistics'] = array(
+    'title' => 'Statistics',
+    'description' => 'View the openings/rate and clicks/CTR for your newsletters',
+    'type' => MENU_NORMAL_ITEM,
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('simplenews_statistics_admin_form'),
+    'access callback' => 'simplenews_statistics_access',
+  );
   $items['admin/content/simplenews/statistics/%node'] = array(
     'title' => 'Statistics Details',
     'type' => MENU_NORMAL_ITEM,
     'page callback' => 'drupal_get_form',
     'page arguments' => array('simplenews_statistics_admin_opens', 4),
-    'access arguments' => array('administer newsletters'),
+    'access callback' => 'simplenews_statistics_access',
+    'access arguments' => array(4),
   );
   $items['admin/content/simplenews/statistics/%node/opens'] = array(
     'title' => 'Newsletter opens',
@@ -34,7 +35,17 @@
     'type' => MENU_LOCAL_TASK,
     'page callback' => 'drupal_get_form',
     'page arguments' => array('simplenews_statistics_admin_clicks', 4),
-    'access arguments' => array('administer newsletters'),
+    'access callback' => 'simplenews_statistics_access',
+    'access arguments' => array(4),
+  );
+  $items['admin/config/simplenews/statistics'] = array(
+    'title' => 'Simplenews Statistics Settings',
+    'type' => MENU_NORMAL_ITEM,
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('simplenews_statistics_admin_settings_form'),
+    'access arguments' => array('administer newsletter statistics'),
+    'file' => 'simplenews_statistics.admin.inc',
+    'weight' => -7.9,
   );
   $items['simplenews/statistics/view'] = array(
     'type' => MENU_CALLBACK,
@@ -50,139 +61,135 @@
 }
 
 /**
- * Implementation of hook_help().
+ * Implements hook_permission().
  */
-function simplenews_statistics_help($path) {
+function simplenews_statistics_permission() {
+  $perms = array(
+    'administer newsletter statistics' => array(
+      'title' => t('administer newsletter statistics'),
+      'description' => t('Allows administrators to administer newsletter statistics. Give to only trusted roles.'),
+    ),
+    'view newsletters statistics' => array(
+      'title' => t('View newsletter statistics'),
+      'description' => t('Allow a roles to access the statistics.'),
+    ),
+  );
+  return $perms;
+}
+
+/**
+ * Access for newsletter statistics.
+ */
+function simplenews_statistics_access($node = NULL) {
+  // If the user has the global permission, allow access.
+  if (user_access('view newsletters statistics')) {
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+/**
+ * Implements hook_help().
+ */
+function simplenews_statistics_help($path, $arg) {
   switch ($path) {
     case 'admin/help#simplenews_statistics':
-      $help = '<p>'. t('Simplenews Statistics gathers the open rate and CTR of a send newsletter.') ."<br />\n";
-      $help .= t("Open Rate is the number of people who open a newsletter divided by the amount of subscriptions.") ."<br />\n";
-      $help .= t("CTR (click-through rate) is the number of people who clicked a link in a newsletter divided by the amount of subscriptions") ."</p>\n";
+      $help = '<p>' . t('Simplenews Statistics gathers the open rate and CTR of a send newsletter.') . "<br />\n";
+      $help .= t("Open Rate is the number of people who open a newsletter divided by the amount of subscriptions.") . "<br />\n";
+      $help .= t("CTR (click-through rate) is the number of people who clicked a link in a newsletter divided by the amount of subscriptions") . "</p>\n";
       return $help;
     case 'admin/content/simplenews/statistics':
-      $help = '<p>'. t('Shows the open rate and CTR for your newsletters.') ."<p>\n";
+      $help = '<p>' . t('Shows the open rate and CTR for your newsletters.') . "<p>\n";
       return $help;
     case 'admin/content/simplenews/statistics/%':
-      $help = '<p>'. t('Shows who and how many times the newsletter has been openend.') ."<p>\n";
+      $help = '<p>' . t('Shows who and how many times the newsletter has been openend.') . "<p>\n";
       return $help;
     case 'admin/content/simplenews/statistics/%/clicks':
-      $help = '<p>'. t('Listing of all clicks for the newsletter.') ."<p>\n";
+      $help = '<p>' . t('Listing of all clicks for the newsletter.') . "<p>\n";
       return $help;
   }
 }
 
 /**
- * simplenews_statistics_view.
- *
- * Gathers the opens
- * @todo: optimise me!
+ * Gathers the opens.
  */
 function simplenews_statistics_view() {
-	
   $stat = _simplenews_statistics_decode($_GET);
- // krumo($stat);
-  if ($stat['mail'] && $stat['nid']) {
-    // Update total
-    _simplenews_statistics_open_add($stat['nid']);
-    
-    $opens = db_select('simplenews_statistics_opens','s')
-	    ->condition('nid',$stat['nid'],'=')
-    	->condition('email',$stat['mail'],'=')
-	    ->fields('s',array('opens'))
-	    ->execute()
-	    ->fetchField();
-    
-    if(!$opens){
-    	$id = db_insert('simplenews_statistics_opens')
-    		->fields(array('nid'=>$stat['nid'],'email'=>$stat['mail'], 'opens'=>1,'timestamp'=>time()))
-    		->execute();
-    }else{
-    	$id = db_update('simplenews_statistics_opens')
-    		->fields(array('opens'=>$opens+1,'timestamp'=>time()))
-    		->condition('nid',$stat['nid'],'=')
-    		->condition('email',$stat['mail'],'=')
-    		->execute();
-    }
-    
+
+  if ($stat['mail'] && $stat['nid'] && variable_get('simplenews_statistics', 1)) {
+    // Process the open for statistics.
+    _simplenews_statistics_open_add($stat);
   }
 }
 
 /**
- * simplenews_statistics_click.
- *
- * Gathers the clicks
- *  @todo: optimise me!
+ * Gathers the clicks.
  */
 function simplenews_statistics_click() {
   $stat = _simplenews_statistics_decode($_GET);
- // krumo($stat);
+
   if ($stat['mail'] && $stat['nid'] && $stat['url']) {
-    // Update total
-    _simplenews_statistics_click_add($stat['nid']);
-    db_insert('simplenews_statistics_clicks')
-	    ->fields(array(
-	    	'email'=>$stat['mail'],
-	    	'nid'=>$stat['nid'],
-	    	'url'=>urldecode($stat['url']),
-	    	'timestamp'=>time()
-	    ))->execute();
-  
+    // Process the click for statistics.
+    if (variable_get('simplenews_statistics', 1)) {
+      _simplenews_statistics_click_add($stat);
+    }
+    // Add Google Aanalytics tracking.
+    _simplenews_statistics_add_ga($stat['url'], $stat['nid']);
+    // Proceed to the actual link.
     drupal_goto(urldecode($stat['url']));
   }
-  
+
   drupal_goto();
 }
 
 /**
- * Implemetation of hook_mail_alter().
+ * Implements hook_mail_alter().
  *
- * Adds a hidden image to the body and counts the amount of emails send
- * 
- * @param $message
- *  Mail message array.
- *  
- *  @todo: optimise me!
+ * Adds a hidden image to the body and counts the amount of emails send.
  */
-function simplenews_statistics_mail_alter(&$message) { 
-	//krumo($message);exit;
-  //if ($message['id'] == 'simplenews_node' && $message['params']['context']['node']->simplenews['s_format']=='html') {
-	if ($message['module'] == 'simplenews' && $message['params']['context']['category']->format =='html') {
-      $nid   = $message['params']['context']['node']->nid;
-      $mail  = $message['params']['context']['account']->mail;
+function simplenews_statistics_mail_alter(&$message) {
     
-      // Need absolute urls
-       _simplenews_statistics_absolute($message['body']);
-      
-      // Parse body
-      _simplenews_statistics_parse_links($message['body'], $nid, $mail);
-      
-      // Add view image
-      _simplenews_statistics_image_tag($message['body'], $nid, $mail);
-      
-      //not count tests email
-      if ($message['key']=='test'){return ;}
-      
-      // Count
-      $sended = db_select('simplenews_statistics','s')
-      	->condition('nid',$nid,'=')
-      	->fields('s',array('send'))
-      	->execute()
-      	->fetchField();
-      
-      if (!$sended) {
-        $id = db_insert('simplenews_statistics')
-        	->fields(array('nid'=>$nid, 'send'=>1))
-        	->execute();
+  if (($message['id'] == 'simplenews_node' || $message['id'] == 'simplenews_test')
+       && $message['params']['simplenews_source']->getFormat() == 'html') {
+    $node = $message['params']['simplenews_source']->getNode();
+    $nid = $node->nid;
+    $mail = $message['params']['simplenews_source']->getRecipient();
+    
+    // Need absolute urls.
+    _simplenews_statistics_absolute($message['body']);
+
+    // Parse body.
+    _simplenews_statistics_parse_links($message['body'], $nid, $mail);
+
+    // Add view image.
+    _simplenews_statistics_image_tag($message['body'], $nid, $mail);
+
+    // Count the number of sent mails for this newsletter if statistics are enabled.
+    // @todo: find a better way for doing this (maybe use Simplenews' built in counter?
+    if (variable_get('simplenews_statistics', 1)) {
+      $found = FALSE;
+      if($resultset = db_select('simplenews_statistics', 'ss')->fields('ss')->condition('ss.nid', $nid)->execute()){
+        foreach($resultset as $result){
+          $found = TRUE; //TODO: there has to be a better way to determine that we have found a record to update
+          db_update('simplenews_statistics')
+            ->fields(array(
+              'send' => ((int) $result->send) + 1,
+            ))
+          ->condition('nid', $nid)
+          ->execute();
+        }
       }
-      else {
-        $id = db_update('simplenews_statistics')
-        	->fields(array('send'=>$sended+1))
-        	->condition('nid',$nid,'=')
-        	->execute();
+      
+      if(!$found){
+        $record = new stdClass();
+        $record->send = 1;
+        $record->nid = $nid;
+        //TODO: add error reporting if insert fails
+        drupal_write_record('simplenews_statistics', $record);
       }
-     // krumo($message,$sended,$id);exit;
+    }
   }
-  
 }
 
 function _simplenews_statistics_absolute(&$body) {
@@ -190,255 +197,320 @@
     foreach ($body as $key => $element) {
       _simplenews_statistics_absolute($body[$key]);
     }
-  }else{  
-    // R to A
+  }
+  else {
+    // R to A.
     $body = preg_replace(
-      '/(src|href)=(\'|")\//',
-      '$1=$2'. url('<front>', array('absolute' => TRUE)),
-      $body
+        '/(src|href)=(\'|")\//', '$1=$2' . url('<front>', array('absolute' => TRUE)), $body
     );
   }
 }
 
-/*
- * Parse links in the body
+/**
+ * Parse links in the body.
  */
 function _simplenews_statistics_parse_links(&$body, $nid, $mail) {
   if (is_array($body)) {
     foreach ($body as $key => $element) {
       _simplenews_statistics_parse_links($body[$key], $nid, $mail);
     }
-  }else{     
-    // Replace links   
+  }
+  else {
+    // Replace links.
     $pattern = '/(<a[^>]+href=")([^"]*)/emi';
     $body = preg_replace($pattern, '"\\1"._simplenews_statistics_replace_url("\2", $nid, $mail)', $body);
   }
 }
 
-/*
- * Add hidden image for view statistics
+/**
+ * Add hidden image for view statistics.
  */
 function _simplenews_statistics_image_tag(&$body, $nid, $mail) {
   if (is_array($body)) {
     foreach ($body as $key => $element) {
       _simplenews_statistics_image_tag($body[$key], $nid, $mail);
-      return; // Only once
+      return; // Only once.
     }
-  }else{  
-    require_once drupal_get_path('module', 'simplenews_statistics') .'/rc4.inc';
-      
-    // Add hidden image
-    $pars = 'nid='. check_plain($nid) .'&mail='. check_plain($mail);
-    $pars = rc4Encrypt(simplenews_private_key(), $pars);
+  }
+  else {
+    require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'simplenews_statistics') . '/includes/rc4.inc';
+
+    // Add hidden image.
+    $pars = 'nid=' . check_plain($nid) . '&mail=' . check_plain($mail);
+    $pars = _simplenews_statistics_rc4Encrypt(simplenews_private_key(), $pars);
     $pars_hash = md5($pars);
-    $url = url('simplenews/statistics/view', array('absolute' => TRUE, 'query' => array('p' => _simplenews_statistics_encode_parameter($pars), 
-                                                                                        'h' => _simplenews_statistics_encode_parameter($pars_hash))));
-    
-    $body .= '<img src="'. $url .'" width="1" height="1">';
+    $simplenews_statistics_internal_url = url('simplenews/statistics/view', array('absolute' => TRUE, 'query' => array('p' => _simplenews_statistics_encode_parameter($pars), 'h' => _simplenews_statistics_encode_parameter($pars_hash))));
+    // Use the "full-url"-option to get an absolute URL.
+    //$shortened_url = shorturl_shorten($simplenews_statistics_internal_url, TRUE);
+    $shortened_url = $simplenews_statistics_internal_url;
+
+    $body .= '<img src="' . $shortened_url . '" width="1" height="1">';
   }
 }
 
 /**
- * Statistics overview
+ * Statistics overview.
+ * @todo: finish
  */
-//altering simplenews_admin_issues_alter
-function simplenews_statistics_form_simplenews_admin_newsletter_issues_alter(&$form, &$form_state){
-	//krumo($form, $form_state);
-	//t('Open rate'), t('CTR'), t('Details')
-	//move operations to end
-	$op = $form['admin']['issues']['#header']['operations'];
-	unset($form['admin']['issues']['#header']['operations']);
-	
-	$header = array(
-		#'stat_sended' => array('data' => t('Emails Sent')),
-		'stat_open_rate' => array('data' => t('Open rate')),
-		'stat_ctr' => array('data' => t('CTR')),
-		'operations' =>$op,
-
-	);
-	$form['admin']['issues']['#header'] += $header;
-	
-	$nids = array_keys($form['admin']['issues']['#options']);
-	//get sended
-// 	$sended = db_select('simplenews_statistics','s')
-// 		->condition('nid',$nids,'IN')
-// 		->fields('s',array('nid','send'))
-// 		->execute()
-// 		->fetchAllKeyed();
-	//get clicks
-// 	$clicks = db_select('simplenews_statistics_clicks','c')
-// 		->condition('nid',$nids,'IN')
-// 		->fields('c',array('nid','url'))
-		#->groupBy('email')
-// 		->execute()
-// 		->fetchAllKeyed();
-	
-	//get opens
-// 	$opens = db_select('simplenews_statistics_opens','o')
-// 		->condition('nid',$nids,'IN')
-// 		->fields('o',array('nid'))
-// 		->groupBy('o.email')
-// 		->countQuery()
-// 		->execute();
-	
-	//get base stats
-	$base_stat = db_select('simplenews_statistics','s')
-		->condition('nid',$nids,'IN')
-		->fields('s')
-		->execute()
-		->fetchAllAssoc('nid');
-	
-	//krumo($form,$nids,$base_stat);
-	
-
-	foreach($form['admin']['issues']['#options'] as $row => $option){
-
-		#$form['admin']['issues']['#options'][$row]['stat_sended'] = !empty($base_stat[$row])?$base_stat[$row]->send : 0 ;
-		$form['admin']['issues']['#options'][$row]['stat_open_rate'] = !empty($base_stat[$row])?$base_stat[$row]->opens : 0 ;
-		$form['admin']['issues']['#options'][$row]['stat_ctr'] = !empty($base_stat[$row])?$base_stat[$row]->clicks : 0 ;
-		$form['admin']['issues']['#options'][$row]['operations'] .= '  '.l(t('stats'), 'admin/content/simplenews/statistics/'.$row);
-	}
-}
-
-
-/**
- * Newsletter opens overview
- */
-function simplenews_statistics_admin_opens($form,&$form_state, $node = NULL) {
-  drupal_set_title(check_plain($node->title).'  '.t('Opens overview'));
+function simplenews_statistics_admin_form($form, &$form_state, $action = 'sent') {
+  drupal_set_title(t('Newsletter Statistics'));
   $form = array();
-  // Build the sortable table header.
-  $header=array(
-  	'email' => array('data' => t('Email'), 'field' => 'o.email'),
-  	'open_time' => array('data' => t('Last open time'), 'field' => 'o.timestamp', 'sort' => 'desc'),
-  	'opens' => array('data' => t('Open(s)'), 'field' => 'o.opens'),
-  );
-  
-  //get data 
-  $query = db_select('simplenews_statistics_opens', 'o')->extend('PagerDefault')->extend('TableSort');
-  $result = $query
-	  ->fields('o')
-  	  ->condition('nid',$node->nid,'=')
-	  ->limit(80)
-	  ->orderByHeader($header)
-	  ->execute();
-  
-  //build table content
-  $options = array();
-  $total_count = 0;
-  foreach ($result as $stat){
-  	$options[]=array(
-  		'email' => $stat->email,
-  		'open_time' => format_date($stat->timestamp),
-  		'opens' => $stat->opens,
-  	);
-  	$total_count = $total_count+$stat->opens;
-  }
-  $form['total_inf'] = array(
-    	'#markup' => '<b>'.t('Total opens').':</b> '.$total_count,
-  );
-  $form['stat_opens'] = array(
-  	'#type' => 'tableselect', 
-  	'#header' => $header,
-  	'#options' => $options, 
-  	'#empty' => t('No stats available'), 
-  );
-  $form['pager'] = array('#markup' => theme('pager'));
 
+  $table = array();
+  $table['header'] = array(t('Title'), t('Newsletter'), t('Date created'), t('Sent'), t('Open rate'), t('CTR'), t('Details'));
+
+  $query = db_select('simplenews_statistics', 'ss');
+  $query->innerJoin('node', 'n', 'n.nid=ss.nid'); //important: we ignore the alias, it could be needed though
+  $query->innerJoin('field_data_field_simplenews_term', 'fdfst', 'n.nid = fdfst.entity_id AND n.vid = fdfst.revision_id');
+  $query->innerJoin('taxonomy_term_data', 'ttd', 'fdfst.field_simplenews_term_tid = ttd.tid');
+  $query->innerJoin('simplenews_category', 'sc', 'fdfst.field_simplenews_term_tid = sc.tid');
+  $query->condition('fdfst.entity_type', 'node');
+  $query->fields('n', array('nid', 'title', 'created'));
+  $query->fields('ttd', array('name'));
+  $query->fields('ss', array('send', 'unique_opens', 'user_unique_click_through'));
+  $query->orderBy('n.created', 'DESC');
+  
+  $resultset = $query->execute();
+  
+  //drupal_set_message('<pre>' . print_r($resultset, 1) . '</pre>');
+
+  $table['rows'] = array();
+  if($resultset->rowCount()){
+    foreach ($resultset as $result) {
+      $table['rows'][] = array(
+        l($result->title, 'node/' . $result->nid),
+        isset($result->name) ? $result->name : t('n/a'),
+        format_date($result->created, 'short'),
+        $result->send,
+        ($result->send > 0) ? round($result->unique_opens / $result->send * 100) . ' %' : t('n/a'),
+        ($result->send > 0) ? round($result->user_unique_click_through / $result->send * 100) . ' %' : t('n/a'),
+        l(t('opens'), 'admin/content/simplenews/statistics/' . $result->nid) . ' ' . l(t('clicks'), 'admin/content/simplenews/statistics/' . $result->nid . '/clicks'),
+      );
+    }
+  } else {
+    $table['rows'][] = array(array(
+      'data' => t('No newsletters available.'),
+      'colspan' => '7',
+    ));
+  }
+  
+  //tables cannot be added to markup - unlike in Drupal 6
+  $form['table'] = array(
+    '#type' => 'fieldset',
+    '#description' => theme('table', $table),
+  );
+  
   return $form;
 }
 
 /**
- * Newsletter clicks overview
+ * Newsletter opens overview.
  */
-function simplenews_statistics_admin_clicks($form,&$form_state, $node = NULL) {
-  drupal_set_title(check_plain($node->title). '  ' .t('Clicks overview'));
+function simplenews_statistics_admin_opens($form, &$form_state, $node = NULL) {
+  drupal_set_title(t($node->title . ' Statistics'));
   $form = array();
-
-  // Build the sortable table header.
-  $header=array(
-    	'url' => array('data' => t('Url'), 'field' => 'c.url'),
-  		'email' => array('data' => t('Email'), 'field' => 'c.email'),
-    	'open_time' => array('data' => t('Click time'), 'field' => 'c.timestamp', 'sort' => 'desc'),
-  );
-  //get data
-  $query = db_select('simplenews_statistics_clicks', 'c')->extend('PagerDefault')->extend('TableSort');
-  $result = $query
-	  ->fields('c')
-	  ->condition('nid',$node->nid,'=')
-	  ->limit(80)
-	  ->orderByHeader($header)
-	  ->execute();
   
-  //build table content
-  $options = array();
-  foreach ($result as $stat){
-  	//krumo($stat);
-  	$options[]=array(
-  		'url'=> l(check_plain($stat->url),$stat->url,array('attributes'=>array('target'=>'_blank'))),
-  	  	'email' => $stat->email,
-  	  	'open_time' => format_date($stat->timestamp),
-  	);
-  }
-  $form['total_inf'] = array(
-  	'#markup' => '<b>'.t('Total clicks').':</b> '.count($options),
+  $table = array(
+    'attributes' => array(),
+    'sticky' => true,
+    'caption' => '',
+    'colgroups' => array(),
+    'empty' => t("Table has no row!"), // The message to be displayed if table is empty
   );
 
-  $form['stat_opens'] = array(
-    	'#type' => 'tableselect', 
-    	'#header' => $header,
-    	'#options' => $options, 
-    	'#empty' => t('No stats available'), 
+  $table['header'] = array(
+    array(
+      'data' => t('Email'),
+      'field' => 'email',
+    ),
+    array(
+      'data' => t('Last open'),
+      'field' => 'timestamp',
+      'sort' => 'desc',
+    ),
+    array(
+      'data' => t('Open(s)'),
+      'field' => 'opens',
+    ),
   );
-  $form['pager'] = array('#markup' => theme('pager'));
+  
+  $query = db_select('simplenews_statistics_opens', 'sso');
+  $query = $query->extend('PagerDefault')->extend('TableSort');
+  $query->fields('sso', array('email'));
+  $query->addExpression('MAX(timestamp) as timestamp');
+  $query->addExpression('COUNT(email) AS opens');
+  $query->condition('nid', $node->nid);
+  $query->limit(20);
+  $resultset = $query->execute();
+  
+  $table['rows'] = array();
+  if(TRUE){ //TODO: the query count
+    foreach($resultset as $result){
+      $table['rows'][] = array(check_plain($result->email), format_date($result->timestamp, 'short'), $result->opens);
+    }
+  } else {
+    $table['rows'][] = array(
+      'data' => t('No statistics available.'),
+      'colspan' => '3',
+    );
+  }
+  
+  // Output of table with the paging
+  $form['table'] = array('#value' => theme('table', $table));
+  $form['pager'] = array('#value' => theme('pager', array('tags' => NULL, 'element' => 0)));
   return $form;
 }
 
 /**
- * Helper functions
- */ 
- 
+ * Newsletter clicks overview.
+ */
+function simplenews_statistics_admin_clicks($form, &$form_state, $node = NULL) {
+  drupal_set_title(t($node->title . ' Statistics'));
+  $form = array();
+  
+  $table = array(
+    'attributes' => array(),
+    'sticky' => true,
+    'caption' => '',
+    'colgroups' => array(),
+    'empty' => t("Table has no row!"), // The message to be displayed if table is empty
+  );
+
+  $table['header'] = array(
+    array(
+      'data' => t('Url'),
+      'field' => 'url',
+    ),
+    array(
+      'data' => t('Time'),
+      'field' => 'timestamp',
+      'sort' => 'desc',
+    ),
+    array(
+      'data' => t('Email'),
+      'field' => 'email',
+    ),
+    array(
+      'data' => t('Click(s)'),
+      'field' => 'clicks',
+    ),
+  );
+  
+  $query = db_select('simplenews_statistics_clicks', 'ssc');
+  $query = $query->extend('PagerDefault')->extend('TableSort');
+  $query->fields('sso', array('url', 'timestamp', 'email'));
+  $query->addExpression('COUNT(url) AS clicks');
+  $query->condition('nid', $node->nid);
+  $query->groupBy('email');
+  $query->groupBy('url');
+  $query->limit(20);
+  $resultset = $query->execute();
+  
+  $rows[] = array(check_plain($row['url']), check_plain($row['email']), $row['clicks']);
+
+  $table['rows'] = array();
+  if(TRUE){ //TODO: the query count
+    foreach($resultset as $result){
+      $table['rows'][] = array(check_plain($result->url), format_date($result->timestamp, 'short'), check_plain($result->email), $result->clicks);
+    }
+  } else {
+    $table['rows'][] = array(
+      'data' => t('No statistics available.'),
+      'colspan' => '4',
+    );
+  }
+  
+  // Output of table with the paging
+  $form['table'] = array('#value' => theme('table', $table));
+  $form['pager'] = array('#value' => theme('pager', array('tags' => NULL, 'element' => 0)));
+  return $form;
+}
+
 /**
-* Alter link to go through statistics
-*/
+ * Alter link to go through statistics.
+ */
 function _simplenews_statistics_replace_url($match, $nid, $mail) {
-  if (substr($match, 0, 1) == "#")
+  if (substr($match, 0, 1) == '#') {
     return $match;
+  }
 
-  require_once drupal_get_path('module', 'simplenews_statistics') .'/rc4.inc';
-  $pars = 'nid='. check_plain($nid) .'&mail='. check_plain($mail) .'&url='. check_url($match);
-  $pars = rc4Encrypt(simplenews_private_key(), $pars);
+  //correct format - do not use module_load_include function
+  require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'simplenews_statistics') . '/includes/rc4.inc';
+  $pars = 'nid=' . check_plain($nid) . '&mail=' . check_plain($mail) . '&url=' . check_url($match);
+  $pars = _simplenews_statistics_rc4Encrypt(simplenews_private_key(), $pars);
   $pars_hash = md5($pars);
+
+  $simplenews_statistics_internal_url = url('simplenews/statistics/click', array('absolute' => TRUE, 'query' => array('p' => _simplenews_statistics_encode_parameter($pars), 'h' => _simplenews_statistics_encode_parameter($pars_hash))));
+  return $simplenews_statistics_internal_url; //when the shorturl module is ready for D7, then we will add support again!
   
-  return url('simplenews/statistics/click', array('absolute' => TRUE, 'query' => array('p' => _simplenews_statistics_encode_parameter($pars), 
-                                                                                       'h' => _simplenews_statistics_encode_parameter($pars_hash))));
+  // Use the "full-url"-option to get an absolute URL.
+  //$shortened_url = shorturl_shorten($simplenews_statistics_internal_url, TRUE);
+
+  //return $shortened_url;
 }
 
- /**
- * Decode a request
+/**
+ * Add Google Analytics codes to a raw encoded/encrypted URL.
+ * @todo write
+ */
+function _simplenews_statistics_add_ga(&$url, $nid) {
+  $ga_tracking = variable_get('simplenews_statistics_ga', 0);
+
+  if ($ga_tracking == 1 && module_exists('googleanalytics')) {
+    $campaign = variable_get('simplenews_analytics_utm_campaign', '!newsletter_title');
+    if ($campaign == '!newsletter_title') {
+      $node = node_load($nid);
+      $campaign = $node->title;
+    }
+
+    if (variable_get('simplenews_statistics_ga_use_hash', 0)) {
+      $url .= '#utm_source=' . drupal_encode_path(variable_get('simplenews_analytics_utm_source', 'newsletter'));
+    }
+    else {
+      if (stristr($url, '?')) {
+        $url .= '&utm_source=' . drupal_encode_path(variable_get('simplenews_analytics_utm_source', 'newsletter'));
+      }
+      else {
+        $url .= '?utm_source=' . drupal_encode_path(variable_get('simplenews_analytics_utm_source', 'newsletter'));
+      }
+    }
+
+    $url .= '&utm_medium=' . drupal_encode_path(variable_get('simplenews_analytics_utm_medium', 'email'));
+    $url .= '&utm_campaign=' . drupal_encode_path($campaign);
+  }
+}
+
+/**
+ * Decode a request.
+ * @todo check
  */
 function _simplenews_statistics_decode($values) {
   $pars = $values['p'];
   $pars_hash = $values['h'];
-  
+
   if (isset($pars) && isset($pars_hash)) {
     $pars = _simplenews_statistics_decode_parameter($pars);
     $pars_hash = _simplenews_statistics_decode_parameter($pars_hash);
-  
+
     if (md5($pars) == $pars_hash) {
-      require_once drupal_get_path('module', 'simplenews_statistics') .'/rc4.inc';
-      $pars = rc4Encrypt(simplenews_private_key(), $pars);
+      require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'simplenews_statistics') . '/includes/rc4.inc';
+      $pars = _simplenews_statistics_rc4Decrypt(simplenews_private_key(), $pars);
       parse_str($pars, $stat);
     }
   }
-  
+
   foreach ($stat as $idx => $stat_param) {
     if ($idx != 'nid' && $idx != 'mail' && $idx != 'url') {
       $idx = str_replace('amp;', '', $idx);
-      $stat['url'] = $stat['url'] .'&'. $idx .'='. $stat_param;
+      if (stristr($stat['url'], '?')) {
+        $stat['url'] = $stat['url'] . '&' . $idx . '=' . $stat_param;
+      }
+      else {
+        $stat['url'] = $stat['url'] . '?' . $idx . '=' . $stat_param;
+      }
     }
   }
-  
+
   return $stat;
 }
 
@@ -450,27 +522,91 @@
   return unserialize(gzuncompress(stripslashes(base64_decode(strtr($par, '-_,', '+/=')))));
 }
 
-/*
- * Add click to total
+/**
+ * Register click.
  */
-function _simplenews_statistics_click_add($nid) {
-	db_query("UPDATE {simplenews_statistics} SET clicks = clicks+1 WHERE nid = :nid",array(':nid'=>$nid));
-}
+function _simplenews_statistics_click_add($stat) {
+  // Check if this is a unique click for the newsletter-mail combination.
+  $query = db_select('simplenews_statistics_clicks', 'ssc');
+  $query->fields('ssc', array('nid'));
+  $query->condition('email', $stat['mail']);
+  $query->condition('nid', $stat['nid']);
+  $query->range(0, 1);
+  $resultset = $query->execute();
+  
+  //TODO: correctly get the count from the resultset object
+  $unique = TRUE;
+  foreach($resultset as $result){
+    $unique = false;
+  }
+  
+  if($unique){
+    $query = db_update('simplenews_statistics');
+    $query->expression('user_unique_click_through', 'user_unique_click_through + 1');
+    $query->condition('nid', $stat['nid']);
+    $query->execute();
+  }
+  
+  // Update the total click amount.
+  $query = db_update('simplenews_statistics');
+  $query->expression('total_clicks', 'total_clicks + 1');
+  $query->condition('nid', $stat['nid']);
+  $query->execute();
 
-/*
- * Add open to total
- */
-function _simplenews_statistics_open_add($nid) {
-  db_query("UPDATE {simplenews_statistics} SET opens = opens+1 WHERE nid = :nid", array(':nid'=>$nid));
+  // Register the individual click.
+  $record = new stdClass();
+  $record->email = $stat['mail'];
+  $record->nid = $stat['nid'];
+  $record->url = urldecode($stat['url']);
+  $record->timestamp = REQUEST_TIME;
+  drupal_write_record('simplenews_statistics_clicks', $record);
 }
 
 /**
- * Implementation of hook_views_api().
+ * Add open to total.
+ */
+function _simplenews_statistics_open_add($stat) {
+  // Check if this is a unique open for the newsletter-mail combination.
+  $query = db_select('simplenews_statistics_opens', 'sso');
+  $query->fields('sso', array('nid'));
+  $query->condition('email', $stat['mail']);
+  $query->condition('nid', $stat['nid']);
+  $query->range(0, 1);
+  $resultset = $query->execute();
+  
+  //TODO: correctly get the count from the resultset object
+  $unique = TRUE;
+  foreach($resultset as $result){
+    $unique = false;
+  }
+  
+  if($unique){
+    $query = db_update('simplenews_statistics');
+    $query->expression('unique_opens', 'unique_opens + 1');
+    $query->condition('nid', $stat['nid']);
+    $query->execute();
+  }
 
-function simplenews_statistics_views_api() {
-  return array(
-    'api' => '2.0',
-    'path' => drupal_get_path('module', 'simplenews_statistics') .'/views',
-  );
+  // Update the total opens amount.
+  $query = db_update('simplenews_statistics');
+  $query->expression('total_opens', 'total_opens + 1');
+  $query->condition('nid', $stat['nid']);
+  $query->execute();
+
+  // Register the individual open.
+  $record = new stdClass();
+  $record->email = $stat['mail'];
+  $record->$stat['nid'];
+  $record->timestamp = REQUEST_TIME;
+  drupal_write_record('simplenews_statistics_opens', $record);
 }
- */
\ No newline at end of file
+
+/**
+ * Implements hook_views_api().
+ */
+// function simplenews_statistics_views_api() {
+//   return array(
+//     'api' => '2.0',
+//     'path' => drupal_get_path('module', 'simplenews_statistics') . '/includes/views',
+//   );
+// }
